Before you start reading, please note that this tutorial is recommended for intermediate coders. Although I will explain everything related to weaponboxs, I won't cover the basic about entities and/or pawn coding, beginners may be confused. If you are un such situation, add a comment with which confused you and I will do my best to clarify it.
Now, let's start by seeing what is a weaponbox entity. According to the wiki, it is "a canister full of specific weapon/ammo". In other words, this entity hold one or more weapons(this will be covered later) and associated ammo for them. It is just a box in which exists a
weapon_* entity. Such entities are created when you drop a weapon.
1.What you need to know before we get into more advanced stuffs is how to create an entity.
Code:
new Entity = create_entity("classname")
//Or by fakemeta
IntClassNameString = engfunc(EngFunc_AllocString, "classname") //cache this value
new Entity = engfunc(EngFunc_CreateNamedEntity, IntClassNameString)
With fakemeta way another step is requiered. You must obtain an offset from the classname string. EngFunc_CreateNamedEntity calls
pfnCreateNamedEntity which expect and integer value of the string. I would recommend to cache this offset, because for the same string it won't be different, and doing this operantion for a huge number of entities is an expensive operation.
Engine does this step internally(
int iszClass = AmxStringToEngine(amx, params[1], len);)
Weaponbox entity is removed on next round, but what you see when you use the snippet above ? This doesn not happen. Why ? I won't go in deep, can read here more:
https://forums.alliedmods.net/showthread.php?p=2235917
It's because for each new entity game create a hash value, and it loop through it. This means that entities are cached, and with amxmodx < 1.8.3 you don't have a way to alter the hash table without orpheu/okapi. In CS, a custom function called CREATE_NAMED_ENTITY exists and it calls both
AddEntityHashValue and
pfnCreateNamedEntity
All this stuffs, about hash table is cs specific. I don't know how it is in other mods, but I guess that simply using
pfnCreateNamedEntity is enough.
Solution for AmxModX < 1.8.3:
PHP Code:
IntClassNameString = engfunc(EngFunc_AllocString, "classname") //cache this value
new OrpheuFunction:HandleCreateNamedEntityFunc = OrpheuGetFunction("CREATE_NAMED_ENTITY")
new Entity = OrpheuCall(HandleCreateNamedEntityFunc, IntClassNameString)
Signature can be obtained from the topic linked above.
Solution for AmxModX >= 1.8.3:
PHP Code:
new Entity = cs_create_entity("classname")
This was a bit generic, for all entities. If you want to create a weaponbox entity, you
must use
weaponbox as classname.
2.Spawning a weaponbox: is enough to call Ham_Spawn on entity index obtained when creating the entity.
3.Adding a weapon to weaponbox:
Ideally, adding a weapon to weaponbox can be done by calling
CWeaponBox:ackWeapon.
PHP Code:
new OrpheuFunction:HandlePackWeaponFunc
HandlePackWeaponFunc = OrpheuGetFunction("PackWeapon" , "CWeaponBox")
OrpheuCall(HandlePackWeaponFunc, WeaponBoxEntity, WeaponEntity)
For parameters:
HandlePackWeaponFunc: the function that will be called
WeaponBoxEntity: the weaponbox entity in which you want to add that weapon
WeaponEntity: the weapon entity that must be added inside weaponbox
In theory a weaponbox can contain as many weapons as you want(but only one weapon of the same type - e.g. you can't have two ak47 inside same weaponbox), but practically it contains only one weapon. This weapons are linked with
m_pNext offset(
const m_pNext = 42 ).
WeaponEntity can be obtained from a player entity's weapon slot with
m_rgpPlayerItems_CBasePlayer offsets(new const m_rgpPlayerItems_CBasePlayer[6] = {367, 368, ...}. It starts from 1(as weapon slots). You should use get_pdata_cbase on them. Keep in mind that
XO_Player is 5.
PHP Code:
new WeaponBoxEntity = OrpheuCall(HandleCreateNamedEntityFunc, IntClassNameString)
new WeaponEntity = get_pdata_cbase(id, m_rgpPlayerItems_CBasePlayer[slot], XO_Player)
if(pev_valid(WeaponEntity))
{
OrpheuCall(HandlePackWeaponFunc, WeaponBoxEntity, WeaponEntity)
}
Game already remove the weapon from player, but you will still see it in your hud. To fix that:
PHP Code:
new WeaponIndex = cs_get_weapon_id(WeaponEntity)
if(CSW_P228 <= WeaponIndex <= CSW_P90)
{
user_has_weapon(index, WeaponIndex, 0)
}
WeaponEntity is weapon entity index, by using
cs_get_weapon_id we obtain the
CSW_* index, which we can use with
user_has_weapon.
4. Adding ammo for that weapon:
This is a bit difficult, more than packing a weapon. I have decided to hook
CWeaponBox:ackAmmo, which calls
CWeaponBox::GiveAmmo after doing some checks regarding ammo name and max ammo for that type.
To understand why I'm saying that is a bit difficult I will provide firstly the code:
PHP Code:
new IntAmmoNames[16]
//Maybe in plugin_init or where you need them
new const StringAmmoNames[][] =
{
"",
"338Magnum",
"762Nato",
"556NatoBox",
"556Nato",
"buckshot",
"45ACP",
"57mm",
"50AE",
"357SIG",
"9mm",
"Flashbang",
"HEGrenade",
"SmokeGrenade",
"C4"
}
new i
for(i = 1; i < sizeof StringAmmoNames; i++)
{
IntAmmoNames[i] = engfunc(EngFunc_AllocString, StringAmmoNames[i]) //caching this
}
new OrpheuFunction:HandlePackAmmoFunc = OrpheuGetFunction("PackAmmo", "CWeaponBox")
new AmmoIndex = ExecuteHam(Ham_Item_PrimaryAmmoIndex, WeaponEntity)
if(AmmoIndex)
{
OrpheuCall(HandlePackAmmoFunc, WeaponBoxEntity, IntAmmoNames[AmmoIndex], ammo)
}
Firstly, convert ammo string to an offset, as we did several times before, but for other strings. Then, find the needed ammo type based on AmmoIndex.
Notice that in CS, weapons like knife/grenades are not mean to be droped. This is a bitsum of weapons with that restriction:
PHP Code:
new const NoDrop = (1 << CSW_HEGRENADE) | (1 << CSW_SMOKEGRENADE) | (1 << CSW_FLASHBANG) | (1 << CSW_KNIFE) |(1 << CSW_VEST) | (1 << CSW_VESTHELM)
Then you can check:
PHP Code:
if(NoDrop & ( 1 << CSW_*))
5.Killing an weaponbox:
At some point you may need to remove a weaponbox, before the game does this automatically. This is pretty simple.
Think() can be called in order to remove it, or
CWeaponBox:Kill
PHP Code:
set_pev(weaponbox, pev_nextthink, get_gametime( ) + 0.1)
call_think(weaponbox)
ExecuteHam(Ham_Think, weaponbox)
//Or with orpheu
new OrpheuFunction:HandleKillFunc
HandleKillFunc = OrpheuGetFunction("Kill", "CWeaponBox")
OrpheuCall(HandleKillFunc, weaponbox)
See that here are several ways of doing it. I would use one of the first 3, it's a bit faster(1 natives vs 2 natives) and does not need a signature file.
WeaponBox offsets can be found here:
https://wiki.alliedmods.net/CWeaponBox_(CS)
WeaponBox functions code can be found here:
https://github.com/Arkshine/CSSDK/bl...ls/weapons.cpp
Now, for the sake of working a bit with offsets, let's look at how we can redo CWeaponBox::HasWeapon.
PHP Code:
new ItemSlot = ExecuteHam(Ham_Item_ItemSlot, WeaponEntity) //get the slot of the weapon we want to check(the one from player)
//m_rgpPlayerItems_CWeaponBox are the items that match player slot from weaponbox
new WpnBoxWeapon = get_pdata_cbase(WeaponBoxEntity, m_rgpPlayerItems_CWeaponBox[ItemSlot], XO_CWeaponBox) //get the weapon from weaponbox that match the slot
//Failed, no valid weapon
if(!pev_valid(WpnBoxWeapon))
{
log_error(AMX_ERR_NATIVE, "Invalid weapon in weaponbox")
return 0
}
//Get first weapon classname
const XO_CWeaponBox = 4
new ItemClassName[20], WeaponClassName[20]
pev(Item, pev_classname, ItemClassName, charsmax(ItemClassName))
while(pev_valid(WpnBoxWeapon))
{
//Get second weapon classname
pev(WpnBoxWeapon, pev_classname, WeaponClassName, charsmax(WeaponClassName))
if(equal(ItemClassName, WeaponClassName))//check is done by classname
{
//If classnames match, then we found something
return 1
}
WpnBoxWeapon = get_pdata_cbase(WpnBoxWeapon, m_pNext, XO_CWeaponBox)//get the next weapon
//As said, weapons are linked with m_pNext offset
//Here we obtain next weapon index from the previous one
}
Some peoples swap the two offset groups:
PHP Code:
//Slots are the same for same weapon
new const m_rgpPlayerItems_CBasePlayer[6] = {367, 368, ...} //items from player. Use XO_Player
new const m_rgpPlayerItems_CWeaponBox[6] = {34, 35, ...} //items from weaponbox. Use XO_CWeaponBox
XO_CWeaponBox,
XO_Player are the difference from windows to linux, when it comes to offset. Linux is: windows offset + XO_*.
For most player offset this is 5, for weapons is 4. This is true for converted offsets, that can be used with get/set_pdata_int/cbase/char/byte and so on.
One more thing that needs to be stated. Game will create a weaponbox with
models/w_weaponbox.mdl. You need to set the right model for the weapon manually.
PHP Code:
new const WeaponBoxModels[][] =
{
"", "models/w_p228.mdl", "",
"models/w_scout.mdl", "", "models/w_xm1014.mdl",
"models/w_c4.mdl", "models/w_mac10.mdl", "models/w_aug.mdl", "",
"models/w_elite.mdl", "models/w_fiveseven.mdl", "models/w_ump45.mdl",
"models/w_sg550.mdl", "models/w_galil.mdl", "models/w_famas.mdl",
"models/w_usp.mdl", "models/w_glock18.mdl", "models/w_awp.mdl",
"models/w_mp5.mdl", "models/w_m249.mdl", "models/w_m3.mdl",
"models/w_m4a1.mdl", "models/w_tmp.mdl", "models/w_g3sg1.mdl",
"", "models/w_deagle.mdl", "models/w_sg552.mdl", "models/w_ak47.mdl",
"", "models/w_p90.mdl", "", ""
}
new WeaponId = cs_get_weapon_id(WeaponEntity)
if(WeaponBoxModels[WeaponId][0] != EOS)
{
engfunc(EngFunc_SetModel, WeaponBoxEntity, WeaponBoxModels[WeaponId])
}
Empty fields are from weapons that can't be dropped.
WeaponEntity is the entity obtained from m_rgpPlayerItems_CBasePlayer. Check is done to prevent crash when trying to apply an empty model.
On weaponbox created by game directly, offsets are not filled until the model is changed(model is no longer
models/w_weaponbox.mdl). This means that
pfnSetModel is send 2 time for a weaponbox.
The signatures used here can be found at:
https://forums.alliedmods.net/showthread.php?t=260124
As always, suggetions/improvements/corrections/questions are welcomed.
__________________