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: Select all
new Entity = create_entity("classname")
//Or by fakemeta
IntClassNameString = engfunc(EngFunc_AllocString, "classname") //cache this value
new Entity = engfunc(EngFunc_CreateNamedEntity, IntClassNameString)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:
Code: Select all
IntClassNameString = engfunc(EngFunc_AllocString, "classname") //cache this value
new OrpheuFunction:HandleCreateNamedEntityFunc = OrpheuGetFunction("CREATE_NAMED_ENTITY")
new Entity = OrpheuCall(HandleCreateNamedEntityFunc, IntClassNameString) Solution for AmxModX >= 1.8.3:
Code: Select all
new Entity = cs_create_entity("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
Code: Select all
CWeaponBox::PackWeaponCode: Select all
new OrpheuFunction:HandlePackWeaponFunc
HandlePackWeaponFunc = OrpheuGetFunction("PackWeapon" , "CWeaponBox")
OrpheuCall(HandlePackWeaponFunc, WeaponBoxEntity, WeaponEntity) 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.
Code: Select all
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)
} Code: Select all
new WeaponIndex = cs_get_weapon_id(WeaponEntity)
if(CSW_P228 <= WeaponIndex <= CSW_P90)
{
user_has_weapon(index, WeaponIndex, 0)
} 4. Adding ammo for that weapon:
This is a bit difficult, more than packing a weapon. I have decided to hook
Code: Select all
CWeaponBox::PackAmmoTo understand why I'm saying that is a bit difficult I will provide firstly the code:
Code: Select all
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)
} Notice that in CS, weapons like knife/grenades are not mean to be droped. This is a bitsum of weapons with that restriction:
Code: Select all
new const NoDrop = (1 << CSW_HEGRENADE) | (1 << CSW_SMOKEGRENADE) | (1 << CSW_FLASHBANG) | (1 << CSW_KNIFE) |(1 << CSW_VEST) | (1 << CSW_VESTHELM)Code: Select all
if(NoDrop & ( 1 << CSW_*))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
Code: Select all
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) Now, for the sake of working a bit with offsets, let's look at how we can redo CWeaponBox::HasWeapon.
Code: Select all
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
} Code: Select all
//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 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.
Code: Select all
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])
} 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.
