[FilterScript] Advanced Inventory/Object Managament Script (DayZ Themed) [SQL;Dynamic;Beta]
#1

INTRODUCTION

As seen in DayZ Standalone! Fully working inventory & object management Script. With complete dynamic features and now easier to edit! Get yourself one while they are fresh!

Featuring a complete GUI and (sort of) easy manipulation in game, pretty much everything is intuitive.

Powered by
ZCMD 0.3.1
MYSQL R39-3
Streamer 2.7.5.2
Sscanf 2.8.1
Crashdetect
SortArray
... And numerous snippets from members of the community (Credited in the script)


DATABASE STRUCTURE

The complete database structure and explanation:

Code:
actions (ActionID, UsesType, ActionName)

ActionID: Internal SQL ID (Primary Key) (INT)
UsesType: The type that the action will be attached to. (INT)
ActionName: The name of the action itself. (VARCHAR)


objectinventory (InventoryID, PlayerObjectID, InsideIDs)

InventoryID: Internal SQL ID (Primary Key) (INT)
PlayerObjectID: The ID of the player object that the inventory belongs to. (INT)
InsideIDs: The ids objects inside of the inventory, separated by commas. (VARCHAR)


objects (ID, Name, Size, UsesType, UsesSlot, SlotsInside, Weight, MaxUses, Display, DisplayColor, DisplayOffsets, OnHandOffsets, OnBodyOffsets, ObjectScales, SpecialFlag_1, SpecialFlag_2, SpecialFlag_3)

ID: Internal SQL ID (Primary Key) (INT)
Name: Name of the base object (VARCHAR)
Size: Size (slots it takes) (INT)
UsesType: ID of the type it uses (INT)
UsesSlot: ID of the slot it uses (INT)
SlotsInside: Amount of slots it haves inside (INT)
Weight: Unused, for scrapped weight (FLOAT)
MaxUses: Total amount of uses (INT)
Display: Object ID to use as dropped object / portraits (INT)
DisplayColor: Color of the object (INT)
DisplayOffsets: RX,RY,RZ and Zoom of the portrait (INT)
OnHandOffsets: X, Y, Z, RX, RY, RZ of the object while on hands (VARCHAR)
OnBodyOffsets: boneid, X, Y, Z, RX, RY, RZ of the object while on body (VARCHAR)
ObjectScales: X, Y, Z that the object will use as scale for both OnHand and OnBody (VARCHAR)
SpecialFlag_1: Special flag value 1 (INT)
SpecialFlag_2: Special flag value 2 (INT)
SpecialFlag_3: Special flag value 2 (INT)


playerinventories (PlayerName, ...)

PlayerName: name of the player attached to the inventory (VARCHAR)
<number>: ID of the slot, these are dynamic columns and are created/removed by altering the slots.


playerobjects (PlayerID, PlayerName, BaseObjectID, CurrentUses, Position, Status, Condition, WorldX, WorldY, WorldZ, P_SpecialFlag_1, P_SpecialFlag_2)

PlayerID: Internal SQL ID (Primary Key) (INT)
PlayerName: Name of the owner if applies (VARCHAR)
BaseObjectID: ID of the base object it uses (INT)
CurrentUses: Current amount of uses it has if applies (INT)
Position: Current position inside of a object/as a container (INT)
Status: Current Status -> 1: Is a player owned container  2: Is inside any container   3: Is dropped    4: To be deleted next script reload.   5: Dropped object on hand.  (INT)
Condition: Unused, default 100. (INT)
WorldX: Position X inside of world. (FLOAT)
WorldY: Position Y inside of world. (FLOAT)
WorldZ: Position Z inside of world (FLOAT)
P_SpecialFlag_1: Player Special flag value 1 (INT)
P_SpecialFlag_2: Player Special flag value 2 (INT)


slots (SlotID, SlotName, MaxObjects)

SlotID: Internal SQL ID (Primary Key) (INT)
SlotName: Name of the slot (VARCHAR)
MaxObjects: Max amount of objects to allow in that slot as containers (INT)


types (TypeID, TypeName)

TypeID: Internal SQL ID (Primary Key) (INT)
TypeName: Name of the Type (VARCHAR)
Notes
- What are special flags? These are used as optional fields which serve a porpuse for the script. These values could have pretty much their own column, but creating columns for each type of object that you want to implement would take a lot of space and make a big cluster of data; instead, you use the special flags to give objects optional values, and script their functionality based upon them. For example, 'SpecialFlag_1', for objects that have a weapon type (2, 12), it represents the GTA SA weapon id of the weapon itself, and 'SpecialFlag_2' determines the caliber of the weapon, which is determined at 'SpecialFlag_1' for bullet & magazines type of objects (6, 7).

- 'playerinventories' is a dynamic table which gets altered in the game itself when you create or remove a object slot. For each slot you add, a new column appears with a name of the ID of the slot, and this is used to keep track of the player inventories as in how many objects inside that slot the player has.


SCRIPT ARCHITECTURE

In the previous version, the whole architecture was based off the SQL, and every time you needed a variable or a configuration of an object you would have to access the SQL, if you didn't have the value stored in a limited memory (the objects that were displayed).
On the new version, the architecture was completely rewritten, moving everything to the memory. The SQL is accessed very few times and everything is stored into the memory, which allows more clean, faster and effective manipulation and usage of the data.
Not only the whole script architecture was rewritten, as in completely all the callbacks and functions modified, but having access to the whole object information in the memory allowed me to make more flexible functions and defenitely easier to read and modify functions.


Before we begin, here a glossary:

PLAYER OBJECT: Actual in game object, which can be owned by a player or not.
BASE OBJECT: The object template that all the player objects use

SOURCE: The source is the ID of the container an object is inside if applies
DESTINATION: The destination is the ID of the container that an object is getting moved to if applies

SELECTED OBJECT: ID of the object that the player has selected
SECOND OBJECT: ID of the object that the player has clicked with a selected object




This is a list of the new enumerators and attached variables:

Code:
enum PlayerObjectInfo
{
	BaseID,
	PlayerID,
 	OwnerName[24],
	CurrentUses,
	Position,
	Status,
	Condition,
	Float:WorldX,
	Float:WorldY,
	Float:WorldZ,
	P_SpecialFlag_1,
	P_SpecialFlag_2,
	InventoryID,
	Inventory[64],
	GameObject,
	AreaID,
	IsNear[PLAYERS]
}
new ObjectInfo[MAX_PLAYER_OBJECTS][PlayerObjectInfo];
new LastObjectInfoIndexUsed, TotalLoadedPlayerObjects;
// Stores player object data into the memory

enum eObjectData
{
	ID,
	Name[32],
	Size,
	UsesType,
	SlotsInside,
	UsesSlot,
	MaxUses,
	Float:Weight,
	Display,
	DisplayColor,
	Float:DisplayOffsets[4],
	Float:OnHandOffsets[6],
	Float:OnBodyOffsets[7],
	Float:ObjectScales[3],
	SpecialFlag_1,
	SpecialFlag_2,
	SpecialFlag_3
}
new ObjectData[MAX_BASE_OBJECTS][eObjectData];
new LastObjectDataIndexUsed, TotalLoadedBaseObjects;
// Stores each object individual information


enum eSlotData
{
	SlotID,
	SlotName[32],
 	MaxObjects
}
new SlotData[MAX_SLOTS][eSlotData];
new LastSlotDataIndexUsed, TotalLoadedSlots;
// Stores each object individual information

enum PlayerVariables
{
	ContainersInPages[13], //MAX_CONTAINERS_LIMIT / MAX_CONTAINERS_PER_PAGE rounded up
	DroppedContainersInPages[13], //MAX_CONTAINERS_LIMIT / MAX_CONTAINERS_PER_PAGE rounded up
	ContainerStoredInSlot[MAX_CONTAINERS_PER_PAGE+1], //+1 for the onhand inventory
	DroppedContainerStoredInSlot[MAX_CONTAINERS_PER_PAGE],
	ActionStored[MAX_OBJECT_ACTIONS],
	ObjectInAction,
	ObjectInActionGlobal,
	ObjectInActionSource,
	CurrentListTotal,
	CurrentListPerPage,
	CurrentListPage,
	CurrentListTotalPages,
	CurrentListStorage[MAX_LIST_ITEMS],
	InventoryOpen,
	ContainersListingPage,
	DroppedContainersListingPage,
	ContainersListingMin,
	DroppedContainersListingMin,
	SelectedObjectID,
	SelectedContainerID,
	SelectedObjectSourceID,
	SelectedObjectGlobal,
	EdittingObjectID,
	EdittingActionID,
    	EdittingSlotID,
	EdittingTypeID,
	EdittingListItem,
	OnHandObjectID,
	OnHandTypeID,
	OnHandWeaponID,
	OnHandAmmoObjectID,
	OnHandMagObjectID,
	OnHandSourceID,
	OnHandSourcePosition,
	ObjectStoredInIndex[10],
	ActionSwapStep,
	HasInvalidAmmo,
	WearingArmor,
	Float:DisplayingModelRotation,
	LastClickedObjectTick,
	LastClickedObjectID,
	OverridePosition,
	MemorySlot[2],
	Float:SelectedObjectHeaderY[MAX_CONTAINERS_PER_PAGE],
	HideTooltipTimerID,
	PlayerSlots[MAX_SLOTS]
}
new PlayerVar[PLAYERS][PlayerVariables];


enum eTypeInfo
{
	TypeID, //ID in database
	TypeName[32] //Name of the type itself
}
new TypeData[MAX_OBJECT_TYPES][eTypeInfo];
new LastTypeDataIndexUsed, TotalLoadedTypes;

enum eActionInfo
{
	ActionID, //ID in database
	TypeIDAttached, //ID of the type that the action goes with
	ActionName[32] //Name of the action itself
}
new ActionData[MAX_TOTAL_ACTIONS][eActionInfo];
new LastActionDataIndexUsed, TotalLoadedActions;


enum eGlobalInfo
{
	ScriptLoaded
}
new GlobalData[eGlobalInfo];
When I reworked the architecture, I assigned the variables index to their corresponding object id, (Object ID 13 would be stored at ObjectData[13][eObjectData]), however I realized that IDs get skipped a LOT in the database (as objects are created and removed very frequentely), so I reworked it for a third time and the objects were stored on indexes independent to their ID (Object ID 13 could be ObjectData[7][eObjectData])

Below I will list every single function included in the script and it's explanation:

Internal Functions
Code:
- GetPlayerObjectMemory(PlayerObjectID)
Returns the memory index (for ObjectInfo) where the PlayerObjectID is stored.

- GetObjectDataMemory(BaseObjectID)
Returns the memory index (for ObjectData) where the Object Base ID is stored.

- GetPlayerObjectDataMemory(PlayerObjectID)
Returns the memory index (for ObjectData) where the PlayerObjectID is stored

- GetActionDataMemory(fActionID)
- GetSlotDataMemory(fSlotID)
- GetTypeDataMemory(fTypeID)
Returns the memory index where any of those is stored (for ActionData, SlotData and TypeData respectively)

- GetObjectBaseID(PlayerObjectID)
Returns the player object base ID

- ConnectMySQL();
Initializes the connection to the SQL server.

- LoadObjectData(); 
Called automatically when the script initializes the connection with the SQL server.

- LoadPlayerObjects();
Called after object data is loaded.

- LoadActionsAndTypesData();
Called after player objects are loaded.

- LoadTypesData()
Called after actions are loaded.

- LoadSlotsData();
Called after types are loaded.

- CheckIfPlayerHasInventory(playerid);
Checks if the playerid has an inventory in the database, if not, create it.

- OnPlayerInventoryCreated(playerid);
Called when the inventory is created.

- LoadPlayerInventory(playerid)
Called after inventory creation/player login.

- OnNewPlayerObjectAdded(Float:fX,Float:fY,Float:fZ, SpawnUses, BaseObjectID)
Called after a new player object is created using SpawnObject

Float:fX, Float:fY, Float:fZ: Coordinates to spawn the object at.
BaseObjectID: The base object ID that the new player object will use
SpawnUses: Amount of uses to spawn the object with

- DeleteBaseObject(BaseObjectID)
Removes BaseObjectID from the game and the database.

- HideOnHandObjectInventory(playerid)
Hides the inventory of your on hand object

- LoadPlayerNearContainers(playerid)
Loads all the containers that the player is near and lists them under "Near objects"

- LoadNearObjectInventory(playerid, ContainerObjectID, memslot)
Loads all the objects inside the corresponding dropped Container Object ID

memslot: Memory index where the container is stored at ObjectStoredInDroppedContainer[][][memslot]


- LoadPlayerContainers(playerid)
Loads all the containers owned by the player and lists them under "Your objects"

- LoadObjectInventory(playerid, ContainerObjectID, memslot)
Loads all the objects inside the corresponding player Container Object ID

memslot: Memory index where the container is stored at ObjectStoredInContainer[][][memslot]


- LoadOnHandObjectInventory(playerid, ObjectID)
Loads the first seven objects inside your Hand object

- OnObjectInsideChecked(ObjectID)
Called when removing an object, internal check to remove it from all inventories.

- CheckBulletLimit(playerid, BulletObject, BulletSource, BulletSourceType, Dest)
This callback is called when moving a bullet object type inside of a weapon or a magazine, checks if the bullet has to split to fit the new weapon or mag, and if it does, create a new object at Dest with the new splitted bullet.

playerid: Player effecting the callback.
BulletObject: the ID of the Player Object of the Bullet
BulletSource: the ID of the container that contains the bullet
BulletSourceType: Type ID of the container that contains the bullet
Dest: the destination of the BulletObject

- CreateNewSplittedObject(playerid, ObjectDataID, Dest, DestType, fUses, Owner[])
Called after CheckBulletLimit detects that the bullet needs to be split and actually inserts the new object into the database.

ObjectDataID: the base object that the new bullet will use
DestType: the type ID of the destination container
fUses: amount of uses to create the object with
Owner: the new owner of the object

- InsertObjectInventory(playerid, Type, Dest, DestType, ObjectDataID, fUses)
Called after CreateNewSplittedBullet inserts the new object into the database, this sets up the object in the memory

- OnObjectSplitted(playerid, Type, Dest, DestType, Object)
Called after all the previous steps are completed, which moves the object into it's new destination.


- UpdateObjectLastPosition(playerid, object)
Called when the player adds a container to his inventory, to set the object on the last position available.


- RefreshObjectInventoryForNear(ObjectID, Exclude = INVALID_PLAYER_ID)
Refreshes the NearObjects of all players but the Excluded that are near the ObjectID 

- RemoveObjectFromNearPlayers(ObjectID)
Removes the object from Near Objects from players that are Near that object.


- DestroyNearInventoryObjects(playerid)
Deletes all the Near objects text draws

- DestroyActions(playerid)
Destroys all the actions text draws

- DestroyInventoryObjects(playerid)
Destroys all the player containers text draws


- CreateInventory(playerid)
- DestroyInventory(playerid)
- ShowInventoryBase(playerid)
- HideInventoryBase(playerid)
Manipulates the inventory base text draws.


- TDTip(playerid, tip[], time = 6000)
- TDInfo(playerid, info[], time = 6000)
- TDAdmin(playerid, message[], time = 6000)
- TDWarning(playerid, warning[], time = 6000)
- Usage(playerid, error[], time = 6000)
- TDError(playerid, error[], time = 6000)
- TDOther(playerid, error[], time = 6000)
Send a message to the player in the form of a textdraw.

- SendTDMessage(playerid, type, fstring[], {Float, _}:...)
Sends a formatted TD message of the type that you input

TYPE_ERROR  	1
TYPE_INFO   	2
TYPE_ADMIN  	3
TYPE_WARNING    4
TYPE_USAGE      5
TYPE_TIP        6
TYPE_OTHER      999


- GeneralTxtHide(playerid)
- CreatePlayerTextdraws(playerid)
- DestroyPlayerTextdraws(playerid)
Manipulates all the general player text draws


- RenderMessage(top, color, const text[])
- RenderMessageToAll(color, const text[])
Sends a rendered message to the player/to all.

- RenderFormattedMessage(playerid, color, fstring[], {Float, _}:...)
Sends a formatted rendered message to the player (to all if INVALID_PLAYER_ID)


- GetWeaponSlot(weaponid)
Returns the slot of the weapon
Player Object Manipulation Functions & Callbacks
Code:
- DropObject(playerid, PlayerObjectID, PlayerObjectType, SourceContainer)
This function is to be used when you want to drop an object from a player. This removes all special flags, unrender all the player containers, removes the object from it's source, updates the player inventories, etcetera.

playerid: The player that effects the action.
PlayerObjectID: The Object ID to drop.
PlayerObjectType: Type ID of the player object.
SourceContainer: The source of the Player Object.


- DropObjectOnPosition(playerid, PlayerObjectID, Float:fX, Float:fY, Float:fZ)
Drops the PlayerObjectID on the selected position (fX, fY, fZ)

playerid: player that has dropped the object, -1 if not applies.

- SpawnObject(Float:fX,Float:fY,Float:fZ, BaseObjectID, SpawnUses)
Creates a new player object.

- SetObjectUses(playerid, ObjectID, iUses)
Set the Current Uses of the ObjectID

- RemoveObjectFromDatabase(ObjectID, bool:inventoryerase)
Removes completely a Player object from the database, erases inventory if true and removes it from any inventory it contains that object, also resets the special flags of all the objects that use the deleted object ID.

- PutObjectInFirstEmptySlotPla(playerid, Object, SourceContainer = 0, Exclude = 0, bool:Drop = true)
Puts the object in the first player container that can hold the object

playerid: The player which will own the object
Object: the actual player object ID
SourceContainer: The container that contains the Object
Exclude: Exclude a certain container to search in
Drop: If true, it will drop the object if it wont find a space, if false, it will just return 0

- PutObjectInFirstEmptySlotCont(playerid, Object, SourceContainer, DestinationContainer, bool:Drop = true)
Puts the object in the first empty slot inside a certain container

DestionationContainer: the container where to put the Object


- MoveObjectToObject(playerid, pos, PlayerObjectID, PlayerObjectType, SourceContainer, SourceType, DestinationContainer, DestinationType, NewOwner, OldOwner[24])
The main callback to move a object to another object

playerid: playerid that called the function, INVALID_PLAYER_ID if none
pos: The position where to move the object at. If pos = -1, it will attempt to move the object to the first empty slot inside the DestinationContainer, if it has no room, it then searches for any other container, and if it still has no room, it will drop the object to the ground. If pos = -2, it will attempt to put the object wherever it fits in DestinationContainer, and if it doesn't, do not allow the move at all.
PlayerObjectID: the ID of the player object to move
PlayerObjectType: the Type ID of the PlayerObjectID
SourceContainer: The source container of PlayerObjectID
SourceType: Type ID of the source container
DestinationContainer: The player id of the container to move the object to
DestinationType:  Type ID of the destination container
NewOwner: Player ID of the new owner
OldOwner: Name of the old owner

- OnObjectMovedAttempt(playerid, PlayerObjectID, PlayerObjectType, SourceContainer, SourceType, DestinationContainer, DestinationType, NewOwner)
This is called at the start of MoveObjectToObject, if this returns 0, it stops the MoveObjectToObject, use this callback to check for conditions when moving objects.

- OnServerObjectMoved(playerid, PlayerObjectID, PlayerObjectType, SourceContainer, SourceType, DestinationContainer, DestinationType, NewOwner)
This is called at the end of MoveObjectToObject, use this callback to set all the SpecialFlags and variables needed, as if this was called, the move was successful.


- InternalSwapObject(playerid, ObjectsSource, SelectedObject, SecondObject, SelectedObjectType, SecondObjectType, SecondObjectSourceType, SelectedObjectSourceType, SelectedObjectPosition, SecondObjectPosition, mem)
This function is to be used when swapping objects that are in the same containers (hence the internal), as it simply only swaps the positions.

playerid: Player that effected the Swap
ObjectSource: Source of the Selected Object
SelectedObject: The object that the player has selected (Also First Object, or Object)
SecondObject: The object the player clicked on
SelectedObjectType: The type of the selected object
SecondObjectType: the type of the second object
SecondObjectSourceType: the type ID of the source of the second object
SelectedObjectSourceType: the Type ID of the source of the selected object
SelectedObjectPosition: The position of the first (selected) object
SecondObjectPosition: The position of the second object
mem: Memory slot of where the SecondObjectContainer is stored at (this is normally the third variable of GlobalObjectsSlots and ObjectStoredInDroppedContainer, etc.)

- SwapObjectWithObject(playerid, SelectedObjectSource, SecondObjectSource, SelectedObject, SecondObject, SelectedObjectType, SecondObjectType, SecondObjectSourceType, SelectedObjectSourceType, SelectedObjectPosition, SecondObjectPosition, mem)
This function shares the parameters with it's Internal sister; this is to be used when the objects being swapped are in different containers.

- OnObjectSwapAttempt(playerid, SelectedObjectSource, SecondObjectSource, SelectedObject, SecondObject, SelectedObjectType, SecondObjectType, SecondObjectSourceType, SelectedObjectSourceType)
This is a callback, this is caleld at the start of both Swap functions, if returned 0, it will stop the swap, use this to check for conditions.
IMPORTANT: this is also the source of the scripted actions; if you want to create special actions (actions that pop up when you click an object with another object, such as the bullets ones) THIS IS THE PLACE, you will find already done examples inside of this callback.

- OnObjectSwapped(playerid, SelectedObjectSource, SecondObjectSource, SelectedObject, SecondObject, SelectedObjectType, SecondObjectType, SecondObjectSourceType, SelectedObjectSourceType)
This callback is called at the end of both swapping functions, use this to set all the variables as if this was called the swap was successfull.


- AddObjectToObject(playerid, object, dest)
Adds an object to the dest inventory

dest: The ID of the destination of the object.

- RemoveObjectFromObject(playerid, object, source)
Removes an object from the source inventory

source: The ID of the source of the object.


- OnPlayerClickAction(playerid, ObjectID, fActionID, fActionName[], ObjectType, ActionObjectUses, aTotalUses, Flag1, Flag2, Flag3, objectsource, SpFlag1, SpFlag2, mem1, mem2)
This callback is called when the player clicks an option from the action menu.

playerid: player that clicked the action.
ObjectID: ID of the object that the action was clicked on.
fActionID: ID of the action clicked.
fActionName: Name of the action clicked.
ObjectType: Type of the Object that the action was clicked on.
ActionObjectUses: Current uses of the object that was clicked on.
aTotalUses: Total uses of the object that was clicked on.
Flag1, Flag2, Flag3: SpecialFlags of the base object of the ObjectID
objectsource: Source of the ObjectID
SpFlag1, SpFlag2: Player special flags of the ObjectID
mem1: position of the Object
mem2: Position of the container in the memory.


- SplitAmmo(playerid, ObjectID, newammo, oldammo, ObjectSource)
Function to split an object (used for ammo)

playerid: Player effecting the action
ObjectID: The player ObjectID to split
newammo: Amount of uses that the new splitted object will have
oldammo: Amount of uses that the ObjectID will have left
ObjectSource: source of the ObjectID

- CheckForBulletCombining(playerid, destobject, destuses, desttotaluses, source, source_source, destcaliber)
Function to check for combining bullets

destobject: The Object that will get combined with the player's selected object
destuses: Uses of the player's SelectedObject
desttotaluses: Uses of the player's SelectedObject
source: The player's selected object ID
source_source: Source of the player's selected object ID
destcaliber: The caliber (Stored in Special Flags) of the destobject


- OnPlayerPutObjectInHand(playerid, object, object_type)
Called when the player puts any object on his hand

object: ID of the player object
object_type: Type of the player Object.

- OnPlayerPutWeaponOnHand(playerid, WeaponObjectID, TypeOfWeapon)
Called inside of OnPlayerPutObjectInHand, when it detects that the object that was put in the player's hand was a weapon.

- OnPlayerRemoveWeaponFromHand(playerid)
Called when the player removes a weapon from his hand.

- CheckOnHandWeaponAmmo(playerid)
Function used to check the player's current ammo inside of the weapon, and set all the corresponding values (remove the weapon if empty, etc.)


- OnObjectDropped(playerid, object, Float:fX, Float:fY, Float:fZ)
Called when an object is dropped.


- OnPlayerRequestActionList(playerid, PlayerObjectID, memoryslotused, PlayerObjectType, Container, bool:Global)
Called when the player double clicks an object, or when the player clicks an object with another object and there is an scripted action to happen.

PlayerObjectID: the ID of the objec that the player clicked.
memoryslotused: Position of textdraw of the PlayerObjectID (the textdraw index).
PlayerObjectType: Type ID of the player object.
Container: ID of the container that was clicked if applies.
Global: whether the object is global or not.
Extra player functions & callbacks
Code:
- UnrenderPlayerContainer(playerid, ObjectID)
- RenderPlayerContainer(playerid, ObjectID)

Renders/unrenders the player object ID as Attached Object on the body.


- OnPlayerEquipContainer(playerid, ObjectID)
- OnPlayerUnEquipContainer(playerid, ObjectID)

Called when the player equips/unequips a container inside it's inventory.
Data Manipulation
Code:
- CountObjectsInInventory(ObjectID)
Returns amount of objects inside the inventory of the inputted Object Player ID

- CountPlayerContainers(playerid)
Returns amount of containers a plyer owns

- GetPlayerContainers(playerid)
Returns containers of a player as a string, IDs separated by commas.

- IsContainerSlotFree(Container, Slot)
Returns 1 if the Slot on the Container is free, 0 if it isn't

- CountEmptySlotsAfterPosition(Container, fPosition)
Returns amount of empty slots inside of Container after fPosition

- FindFirstEmptySlotInContainer(Container, start = 0)
Returns the first empty slot in the Container, starting from any position

- CheckIfObjectFitsInObject(ObjectToCheck, InsideOfObject, NewPosition)
Returns 1 if the ObjectToCheck fits inside of InsideOfObject beginning from NewPosition

- CheckIfObjectFitsInPlaceOf(ObjectToCheck, SecondObject, SecondObjectContainer)
Returns 1 if the ObjectToCheck fits in place of the SecondObject that is inside SecondObjectContainer
In Game Object Related Functions
Code:
- SetObjectColors(GTAObjectID, PlayerObjectID, ObjectBaseID = -1)
Recolors an object dropped if it's not a GTA SA weapon

GTAObjectID: The SAMP object ID
PlayerObjectID: The ID of the player object
ObjectBaseID: The Base Object ID of the player object
Administration Related Functions
Code:
- ListActions(playerid, ItemsPerPage, Page)
Creates a dialog listing all the actions

- ListTypes(playerid, ItemsPerPage, Page)
Creates a dialog listing all types

- ListSlots(playerid, ItemsPerPage, Page)
Creates a dialog listing all slots

- ListObjects(playerid, ItemsPerPage, Page)
Creates a dialog listing all objects

playerid: player to show the dialog
ItemsPerPage: how many items in one page
Page: page to display

- EditActions(playerid, fActionID)
Brings up the action editing panel for that Action ID.

- OnPlayerCreateAction(playerid)
Called when a new action is created, the ID is the cache_insert_id

- DeleteAction(fActionID)
Deletes the fActionID from the memory and the database.

- OnPlayerCreateType(playerid)
Called when a new type is created, the ID is the cache_insert_id

- DeleteType(iTypeID)
Deletes the iTypeID from the memory and the database.

- EditType(playerid, ifTypeID)
Brings up the action editing panel for that Type ID.

- PlayerEditSlot(playerid, fSlotID)
Brings up the action editing panel for that Slot ID.

- OnPlayerCreateSlot(playerid)
Called when a new slot is created, the ID is the cache_insert_id

- DeleteSlot(fSlotID)
Deletes the slot from the memory and the database.

- OnPlayerCreateBaseObject(playerid, Duplicate)
Called when the player creates a new object via /newobject

Duplicate: if the new object should be a duplicate of a currently existing base object. (0 for none)

- PlayerEditBaseObject(playerid, BaseObjectID)
Brings up the action editing panel for that Object ID.
Limits & Definitions

MAX_PLAYER_OBJECTS: 32768 - Max amount of player objects that can be loaded into the memory from the database (affects heavily the .amx size)
MAX_BASE_OBJECTS: 2048 - Max amount of base objects that can be loaded into the memory from the database
MAX_OBJECT_TYPES: 150 - Max amount of types that can be loaded into the memory from the database
MAX_TOTAL_ACTIONS: 150 - Max amount of actions that can be loaded into the memory from the database
MAX_SLOTS: 50 - Max amount of slots that can be loaded into the memory from the database
MAX_OBJECT_ACTIONS: 3 - Max actions that can be listed in an object when double clicked
MAX_CARRY_OBJECTS: 40 - Max inventory slots for a single object
MAX_LIST_ITEMS: 20 - Max items that can be listed in one list

MAX_CONTAINERS_PER_PAGE: 4 - Max amount of containers that can be displayed at once (separated for global and player containers)
MAX_CONTAINERS_LIMIT: 50 - Max amount of containers to be loaded in the memory at once (separated for global and player containers)

INVALID_SLOT_ID MAX_SLOTS-1
INVALID_BASEOBJECT_ID MAX_BASE_OBJECTS-1
INVALID_PLAYEROBJECT_ID MAX_PLAYER_OBJECTS-1
INVALID_ACTION_ID MAX_OBJECT_ACTIONS-1
INVALID_TYPE_ID MAX_OBJECT_TYPES-1


Non Listed Variables

pawn Code:
/*These are all player textdraws, since most use More than two dimensions I can't list them in PlayerVar*/
new PlayerText:Inv[PLAYERS][24],
    PlayerText:GeneralTxt[PLAYERS][2],
    PlayerText:InventoryObjectsHead[PLAYERS][5][MAX_CONTAINERS_PER_PAGE],
    PlayerText:InventoryObjectsSlots[PLAYERS][MAX_CARRY_OBJECTS][MAX_CONTAINERS_PER_PAGE+1], //+1 is the onhand inventory
    PlayerText:InventoryObjectsAmount[PLAYERS][MAX_CARRY_OBJECTS][MAX_CONTAINERS_PER_PAGE],
    PlayerText:GlobalObjectsHead[PLAYERS][5][MAX_CONTAINERS_PER_PAGE],
    PlayerText:GlobalObjectsSlots[PLAYERS][MAX_CARRY_OBJECTS][MAX_CONTAINERS_PER_PAGE],
    PlayerText:GlobalObjectsAmount[PLAYERS][MAX_CARRY_OBJECTS][MAX_CONTAINERS_PER_PAGE],

    PlayerText:ActionMenu[PLAYERS][5];

    /*This can't go into PlayerVar because of the three dimensions*/
new ObjectStoredInContainer[PLAYERS][MAX_CARRY_OBJECTS][MAX_CONTAINERS_PER_PAGE+1], //+1 is the onhand inventory
    ObjectStoredInDroppedContainer[PLAYERS][MAX_CARRY_OBJECTS][MAX_CONTAINERS_PER_PAGE];
   
   
new dbHandle, query[128], medquery[256], bigquery[512], nname[24], msg[144];

USAGE

GAMEWISE
- Press KEY_CTRL_BACK Default 'H' to open the inventory (or type /inventory)
- Select an object by clicking it's portrait.
- You can move around the object by selecting it and clicking on any slot.
- Swap objects by clicking another object with a selected object.
- Open action menu by double clicking any (player) object.
- Swap containers by clicking a container with another container.
- Drop any object by clicking the Near Objects box with a selected object.
- Equip a container by double clicking a dropped container or by clicking your player image with a selected object. (If the object uses slots, it will check if the slot is not occupied by another object, if it is, it will attempt to add the object inside any container)
- Pickup any object by double clicking it, moving it into your player image or moving it directly into one of your objects.
- You can put objects in your hands by clicking the 'Hands' Box with a selected object.
- You can manipulate the on hand inventory the same way you do with the rest of the inventory (except actions).
- Some objects might not allow to be swapped right away as they might have more than one action with the object you have selected.
- You can also move an object to a container by clicking the actual container header box with the object you want to put inside of it.
- Equipping a container will move it to the last position, you can move the container around by swapping it with other containers.
- The script automatically calculates how many lines of containers will be displayed taking in account the size of the containers theyselves. If by counting the lines (Header plus amount of objects/7 or 6, depending which type of container) the object goes below a set Y coordinate, it will stop listing objects and will pass the last object (the one that exceeded the limit Y) to the next page. That's why, if you have two backpacks with 35 slots (6 lines each), they will be separated into two pages, because the second backpack will exceed the defined Y; while if you have one backpack, and two M4s, only one M4 will go to the next page as the first one wont exceed the defined Y. It is all depending how you place your containers. Max amount of containers is 4 per page (was 3).

SCRIPTWISE
- Lots of scripted actions already added. If you wish to add your actions you will need to create the action first (a command in game), after the action is created it will be ready for use, you can script an action by using the OnPlayerClickAction callback, you can help yourself with OnObjectSwapAttempt if you wish to create special interactions between objects.

WARNING: Most of the actually scripted functions will get broken if you delete the types. If you edit an action or a type, make sure to edit the script on wherever the edited type/actions are used!!

- Scripted actions work mainly by the Type ID attached and the Action itself. Special interactions have negative types ids attached so you can control them via the script. If you change an object type and remove it's weapon status, it will no longer function as weapon; if you remove the weapon type all together the script will no longer recognize weapons as the script works heavily by the types and actions ids.

- Use the callbacks OnObjectSwapAttempt, OnObjectMovedAttempt to prevent an object from being moved or swapped if it doesn't fit your needs, and use the callbacks OnObjectSwapped, OnServerObjectMoved to apply new variables (for example, SpecialFlags).

- The magic happens at OnPlayerClickPlayerTextDraw, most of the core functions are called from there. I tried to make the variables names as clear as possible, but doing something wrong in there might screw the whole script up.

- Scripted Actions can be as easy as a few lines (Eat Food, Eat All Food) or as hard as several hundred lines (Weapon system all together). You could try to modify the script and add fuel powered object (objects that require fuel just like weapons require bullets) by using the already scripted weapon system.

- Special Flags are really usefull tools that allow lots of flexibility for the script.

- Status 4 on objects flags them for deletion after the server starts, this is useful to create temporary objects (looting system).

- As already explained, MoveObjectToObject pos parameter offers a lot of flexibility, as -1 will attempt to add the object to the destination container, and if it doesn't fit, into any other container, and if it still doesn't fit, it will drop the object; -2 will attempt to add the object to any slot in the destination container, and if it doesn't fit, prevent the move.

Administration Commands (RCON ADMIN)
Code:
• /objects
- Lists and brings up the object edition menu

• /newobject
- Creates an object and inserts you into it's edition mode

• /remobject <id or part of name>
- Removes a global object from the DB and the memory.

• /spawnobject <ID or part of name>
- Spawns a persistent object in front of the admin.

• /actions
- Brings up the action editing menu.

• /newaction
- Creates a new action and puts you in edit mode.

• /remaction <ID or part of name>
- Removes an action from the DB and the memory

• /types
- Brings up the types editing menu

• /newtype
- Creates a new type and puts you in it's edition mode

• /remtype <ID or part of name>
- removes a type from the Database and the memory

• /slots
- Brings up a list with all the current slots

• /newslot
- Creates a new slot and puts you into it's edit mode.

• /remslot <ID or part of name>
- Removes a slot from the database and the memory
You can use all those commands to modify the in game aspect, you will still need to script actions yourself after you added them in the script (OnPlayerClickAction)

Scripted Actions
At the moment there are several scripted functions included in:

- Swap, Add Into (Attached Type -1): This is an internal action used for bullets interaction with magazines or ammo boxes
- Eat Food, Eat All Food (Attached Type 5): Allows you to eat an item labeled as food (type 5) and consume one or all of the uses, giving you a health value defined in the SpecialFlag_1 of the object base.
- Empty Magazine (Attached Type 6): Empties the magazine and adds the content inside of it into the container the magazine currently is.
- Split (Attached Type 7): Splits the bullet stack into two.
- Check Ammo (Attached Type 6): Checks how many bullets are inside the magazine
- Combine, Swap (Attached Type -2): Scripted actions for interactions between bullets.
- Empty Gun, Empty Bolt Gun (Attached Types 2 & 12): Same as empty magazine but for weapons
- Check Magazine, Check Chamber (Attached Types 2 & 12): Same as check ammo but for weapons
- Swap Container, Add Into Container (Attached Types -3): Internal function for containers to allow them to be swaped or add into interactions.

Slots
Slots are a new feature in the rework that limits the amount of containers you can have. A container is assigned a slot, and then the script keeps track of how many objects of a slot you have equiped. If you reach the Max Objects defined on the slots table, the script wont let you add that object as a container (and will instead try to add it inside your containers).

Swapping option between dropped containers and your containers (new feature) is only given if the objects have the same slot.

The player inventory data is kept in playerinventories database, which featuers a dynamic structure. For each new slot you create with /newslot, a new column is added to the playerinventory data, with the ID of the slot, this way, when the player equips a container, it saves it on the slot column that the container uses. (Columns are also deleted when you remove a slot)

Weapon System
Along with the whole core functions, I also added a fully working weapon script that works exactly like (except the weapon attachments) the DayZ SA weapons. You can put a weapon on your hand, but it wont fire unless it haves ammo or a mag with ammo of it's correspondient caliber.

This was the main thing that made me create the SpecialFlags architecture. When I was creating the weapon system, I realized I needed several fields of data to make the system work, as in, what was the GTA weapon ID of the object when I put it on my hand with ammo? What kind of bullets can it take? How many bullets can it take? What kind of mag can it take?..
Making a column for each field needed would be pretty bad and would look like a cluster when there could be 10 weapons-or-so and 500 other objects with different functions, and then those objects would need custom fields, this is when I came up with the SpecialFlags, which are field of data that can hold an integer, and you can assign that integer to whatever you want in the script, in essence:

SpecialFlag_1, for weapons (Type 2 & 12) determines the GTA SA weapon id to give;
SpecialFlag_2, for weapons (Type 2 & 12) determines the caliber id of the ammo/mags it would take;
SpecialFlag_3, for bolt weapons (Type 12) determines the chamber size;
SpecialFlag_1, for magazines and bullets (Type 6,7) determines the caliber id of them;
SpecialFlag_2, for magazines (Type 6) determines the amount of bullets it can take inside.

On the same way, P_SpecialFlags are player object special flags that work in the same way, but are individual to each player object, in essence:

P_SpecialFlag_1, for weapons (Type 2 & 12), stores the Player Object ID of the mag or the bullet that is inside it's chamber;
P_SpecialFlag_1, for mags (Type 6), stores the Player Object ID of the bullet that's inside of it;
P_SpecialFlag_1, for bullets (Type 7), also stores the Player Object ID of the mag it is inside of;

This way, you can mold your object system any way you want, another example is on the Food items, where SpecialFlag_1 stores the amount of HP it will restore with each use.

Having explained that now what's left is pretty easy: You can dynamically create an object in game. You can assign that object the weapon type, and assign it any SpecialFlags; You can also create another object and assign them the bullet type, also assigning it any SpecialFlags; Finally, combining both, you can make a whole new weapon with any kind of ammo in a few seconds. You could create an AK-74 which doesn't use 7.62x51mm, but 7.62x39mm, instead of the AK-47, or along the AK-47. You could also create a custom damage system based off this, as the AK47 would have more damage (51mm) than the AK74 (39mm).

MEDIA

[ame]http://www.youtube.com/watch?v=bWz4KgubkmI[/ame]

INSTALLATION & DOWNLOAD

Database infrastructure and details at lines 9-11
Code:
#define MYSQL_HOST  "localhost"
#define MYSQL_USER  "root"
#define MYSQL_UPASS ""
#define MYSQL_DB    "inventory"
It is also recommended to change the following variables if you want
Code:
#define PLAYERS 			200     //Max players, affects really heavily the .amx size.

#define 	MAX_PLAYER_OBJECTS  		32768   //Max amount of player objects that can be loaded into the memory from the database (affects heavily the .amx size)
#define 	MAX_BASE_OBJECTS  			2048    //Max amount of base objects that can be loaded into the memory from the database
#define 	MAX_OBJECT_TYPES    		150     //Max amount of types that can be loaded into the memory from the database
#define 	MAX_TOTAL_ACTIONS   		150     //Max amount of actions that can be loaded into the memory from the database
#define     MAX_SLOTS                   50      //Max amount of slots that can be loaded into the memory from the database
#define 	MAX_OBJECT_ACTIONS  		3       //Max actions that can be listed in an object when double clicked
#define 	MAX_CARRY_OBJECTS   		40      //Max inventory slots for a single object
#define 	MAX_LIST_ITEMS      		20      //Max items that can be listed in one list

#define     MAX_CONTAINERS_PER_PAGE    	4       //Max amount of containers that can be displayed at once (both for global and player containers)
#define     MAX_CONTAINERS_LIMIT        50      //Max amount of containers to be loaded in the memory at once (both for global and player containers)
The following includes are used:
Code:
#include <a_samp>
#include <a_mysql>
#include <zcmd>
#include <sscanf2>
#include <streamer>
#include <crashdetect>
#include <SortArray>
Download
GitHub Repository, includes .SQL file with SQL structure


Special Mentions
Slice for his SortArray.
Pottus for being Pottus.
****** because I have to learn how to use foreach.
Reply
#2

screen?
Reply
#3

While the latest ones are being uploaded feel free to check the previous stage ones:

https://sampforum.blast.hk/showthread.php?tid=495512

(Warning: big pictures ahead)

EDIT: Uploaded them on main post
Reply
#4

I saw the pictures this week and it looks that it something very good.

Have you a server with this already implemented to see it ?
Reply
#5

Good job. Very good job. Can u add another link for downloading? I have some problems with your links
Reply
#6

GG +4
Reply
#7

Quote:
Originally Posted by DoKAtemar
View Post
Good job. Very good job. Can u add another link for downloading? I have some problems with your links
Sure, pastebin added.

Quote:
Originally Posted by DarkZeroX
View Post
GG +4
Thanks.

Quote:
Originally Posted by anou1
View Post
I saw the pictures this week and it looks that it something very good.

Have you a server with this already implemented to see it ?
No, i've got no server. Sorry.
Reply
#8

this is very cool man, nice work.
Reply
#9

cant you convert it into Zcmd or strcmp?
nice by the way
Reply
#10

Good effort but there is some serious design dynamic flaws that makes this an absolute nightmare for anyone to try and customize their implementation.

I'll give you some examples.

- Lots of variables that SHOULD be in enums
- Too much code in a single script
- Use of PVars is a huge no-no
- No need to delete playertextdraws when a player disconnects this happens automatically
- This should be a include not a filterscript how is anyone to effectively implement this into a gamemode? Using CallRemoteFunction() and/or PVars is a terrible idea
- No OnPlayerClickTextDraw() so the menu will stay up if you press escape (very annoying), how about when the player dies? Doesn't close either. How about if the player gets shot? That should close the inventory immediately so they can fight

It's a good effort but from my view if I wanted I try and use this there is simply too many problems with this code right now to even give it a try.
Reply
#11

Nice one,
+2 Repulations!
Reply
#12

Quote:
Originally Posted by iBots
View Post
cant you convert it into Zcmd or strcmp?
nice by the way
It is zcmd.



Quote:
Originally Posted by [uL]Pottus
View Post
- Lots of variables that SHOULD be in enums
True. Was already half way done when doing this but changing it shouldn't be too hard.

Quote:
Originally Posted by [uL]Pottus
View Post
- Too much code in a single script
- This should be a include not a filterscript how is anyone to effectively implement this into a gamemode? Using CallRemoteFunction() and/or PVars is a terrible idea
This is not meant to be used from the main gamemode, that's why it's a filterscript itself. (Besides i have no knoweldge whatsoever on making includes). I did plan on making this an include but I really have no idea how and I need to investigate on the matter (That's why there are useful callbacks, that was my main plan at the start), and that's why I've noted multiple times that this is not a plug and play script and it is meant to be modified to fit your needs.
Feel free to instruct me on includes creation, though, I believe you have to hook all the native callbacks and then it'll work?

Quote:
Originally Posted by [uL]Pottus
View Post
- Use of PVars is a huge no-no
We already discussed this, didn't we? I said I would add as many PVars as possible to annoy you. But on a serious note, this can also be fixed easily.

Quote:

- No need to delete playertextdraws when a player disconnects this happens automatically

Not really a script breaking flaw, is it?

Quote:

- No OnPlayerClickTextDraw() so the menu will stay up if you press escape (very annoying), how about when the player dies? Doesn't close either. How about if the player gets shot? That should close the inventory immediately so they can fight

As for the escape one: it is not possible using player text draws and I think OnPlayerClickTextDraw is not called with player textdraws, I helped a player solve this I believe, it might get called if you create an invisible selectable text draw and show it along the player text draws, I might have to test it. Anyways something I didn't mention is that if you press H while the inventory is open it'll close, however since OnPlayerKeyStateChange isn't called while in select mode you must first press escape.
As for the death one: OnPlayerClickPlayerTextDraw(playerid, Inv[playerid][14]); under OnPlayerDeath (I created this entirely with Rivershell from the previous versions as background!) -- as for the shot one, that's up to the scripters to chose, this is not intended for any script type. Plus, on dayz you get killed lots of times while browsing your inventory and fiddling around with it since you're really vulnerable (You should know this...), so it adds this feeling.



As I said this is a beta and I expect feedback to keep modifying the script. I will change the variables to enums, to begin with. And maybe work on the last issue. But again -- I'm not trying for you to use it, nor I intend to or is my job to do so. Use under own discretion.
Reply
#13

The design looks bad, otherwise you did a good job out there!
Reply
#14

Quote:
Originally Posted by Naruto_Emilio
View Post
The design looks bad, otherwise you did a good job out there!
Standard DayZ SA Inventory

http://i1.ytimg.com/vi/_QddzkVf2_E/maxresdefault.jpg

My Inventory

http://i.imgur.com/2iTTfKY.jpg

The design is meant to be like that.
Reply
#15

Well it's pretty easy to make it an include, think of an include as code you simply insert into your gamemode when you use hooking (y_hooks) it's really no different than making a filterscript. The advantage of this is now the gamemode can directly access the include this is where static & stock modifiers are useful.

Everything that won't be used outside of your include should use a static modifier (variables/functions) functions that can be used outside of your include should usually have the stock modifier. You could actually make your system into an include in only a few minutes but keep in mind some functions like IsNumeric() might already be defined.
Reply
#16

Good Work!
Reply
#17

Quote:
Originally Posted by [uL]Pottus
View Post
Well it's pretty easy to make it an include, think of an include as code you simply insert into your gamemode when you use hooking (y_hooks) it's really no different than making a filterscript. The advantage of this is now the gamemode can directly access the include this is where static & stock modifiers are useful.

Everything that won't be used outside of your include should use a static modifier (variables/functions) functions that can be used outside of your include should usually have the stock modifier. You could actually make your system into an include in only a few minutes but keep in mind some functions like IsNumeric() might already be defined.
That's why most of includes define their own functions with the include name prefix "Inventory_IsNumeric()", i guess. Callbacks should be forwarded and used along with another filterscript; the nature of the script wont allow most functions to be stocks since it requieres SQL information which are on threaded queries which requiere public functions, though.

I will keep this as a FS for now, though, as I would have to rethink the mechanics on the already scripted actions (which requiere special portions of code). Thanks for the info.
Reply
#18

Code:
mysql_format(dbHandle, medquery, sizeof medquery, "SELECT * FROM playerobjects \
                JOIN objects ON playerobjects.O_ObjectID = objects.ID WHERE playerobjects.PlayerObjectStatus = 3 OR playerobjects.PlayerObjectStatus = 4");
If query doesn't contain a single parameter, you can simply pass it to mysql_query. Think of format + SendClientMessage (just with additional %e).

Code:
mysql_format(dbHandle, query, sizeof query, "SELECT * FROM objects WHERE Name LIKE('%%%e%%') LIMIT 0,1",Obj);
You might consider additional escaping of Obj string, prefixing LIKE wildcards % and _ with backslash.

/remaction is vunerable to sqli
Quote:

/remaction a' or '1'='1

results in
Quote:

DELETE FROM actions WHERE Action = 'a' or '1'='1'

Code:
if(sscanf(params,"s[32]", id))
Using sscanf to fetch single string is unnecessary, use
Code:
if(isnull(params))
Then params instead id.

Default JOIN is INNER JOIN in mysql engine. Please specify type of join you want, because in most cases it should be LEFT JOIN.

Overall nice system, I like it.

P.S. I hate you forever and ever for raven's rp.
Reply
#19

Quote:
Originally Posted by Misiur
View Post
If query doesn't contain a single parameter, you can simply pass it to mysql_query. Think of format + SendClientMessage (just with additional %e).
Weird that i haven't done it like that (You can check other queries on which I did). Also even weirder that I haven't used IN() instead of just writing that two times.. I guess just a slip :P

Quote:
Originally Posted by Misiur
View Post
/remaction is vunerable to sqli
I really thought I escaped that... fixed; either way it's a RCON admin CMD meant only for development. If the admin is dumb enough to delete all of his actions then.. well.. i've got no words.
I just also noticed other vulnerabilities in the script, I guess I just forgot to escape the data.
EDIT: Even more vulnerabilities. I'm the dumb one.

Quote:
Originally Posted by Misiur
View Post
Code:
if(sscanf(params,"s[32]", id))
Using sscanf to fetch single string is unnecessary, use
Code:
if(isnull(params))
Then params instead id.
As I said it's not really a script breaking thing, and very very minor. I just got used to sscanf when checking parameters. Either ways I would need an additional check to limit the string input to 32.

Quote:
Originally Posted by Misiur
View Post
I did test all the queries before adding them and I got the expected results without problems, since I've got the expected results I had no reason not to use the inner join, I believe this is not completely useful when selecting exact data from two tables as there will be no difference if doing an inner join or a left join.


Quote:
Originally Posted by Misiur
View Post
Overall nice system, I like it.

P.S. I hate you forever and ever for raven's rp.
Why does everyone tell me that?
Reply
#20

I mean can you make it Dini saving instead of mysql?better for everyone
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)