#ifdef CSQC
bool autocvar_cl_ghost_items_vehicle = true;
.vector item_glowmod;
+.bool item_simple; // probably not really needed, but better safe than sorry
void Item_SetAlpha(entity this)
{
bool veh_hud = (hud && autocvar_cl_ghost_items_vehicle);
{
if(this.ItemStatus & ITS_ANIMATE1)
{
- this.angles += this.avelocity * frametime;
+ if(!this.item_simple)
+ this.angles += this.avelocity * frametime;
float fade_in = bound(0, time - this.onground_time, 1);
setorigin(this, this.oldorigin + fade_in * ('0 0 10' + '0 0 8' * sin((time - this.onground_time) * 2)));
}
if(this.ItemStatus & ITS_ANIMATE2)
{
- this.angles += this.avelocity * frametime;
+ if(!this.item_simple)
+ this.angles += this.avelocity * frametime;
float fade_in = bound(0, time - this.onground_time, 1);
setorigin(this, this.oldorigin + fade_in * ('0 0 8' + '0 0 4' * sin((time - this.onground_time) * 3)));
}
Item_SetAlpha(this);
}
-void ItemDrawSimple(entity this)
-{
- if(this.gravity)
- {
- Movetype_Physics_MatchServer(this, false);
-
- if(IS_ONGROUND(this))
- this.gravity = 0;
- }
-
- Item_SetAlpha(this);
-}
-
void Item_PreDraw(entity this)
{
if(warpzone_warpzones_exist)
if(sf & ISF_LOCATION)
{
- this.origin_x = ReadCoord();
- this.origin_y = ReadCoord();
- this.origin_z = ReadCoord();
+ this.origin = ReadVector();
setorigin(this, this.origin);
this.oldorigin = this.origin;
}
this.mdl = "";
string _fn = ReadString();
+ this.item_simple = false; // reset it!
if(autocvar_cl_simple_items && (this.ItemStatus & ITS_ALLOWSI))
{
string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
- this.draw = ItemDrawSimple;
+ this.item_simple = true;
if(fexists(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".md3")))
this.mdl = strzone(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".md3"));
this.mdl = strzone(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".mdl"));
else
{
- this.draw = ItemDraw;
+ this.item_simple = false;
LOG_TRACE("Simple item requested for ", _fn, " but no model exists for it");
}
}
- if(this.draw != ItemDrawSimple)
+ if(!this.item_simple)
this.mdl = strzone(_fn);
this.pushable = true;
//this.angles = '0 0 0';
set_movetype(this, MOVETYPE_TOSS);
- this.velocity_x = ReadCoord();
- this.velocity_y = ReadCoord();
- this.velocity_z = ReadCoord();
+ this.velocity = ReadVector();
setorigin(this, this.oldorigin);
if(!this.move_time)
//WriteByte(MSG_ENTITY, this.cnt);
if(sf & ISF_LOCATION)
{
- WriteCoord(MSG_ENTITY, this.origin.x);
- WriteCoord(MSG_ENTITY, this.origin.y);
- WriteCoord(MSG_ENTITY, this.origin.z);
+ WriteVector(MSG_ENTITY, this.origin);
}
if(sf & ISF_ANGLES)
if(sf & ISF_DROP)
{
- WriteCoord(MSG_ENTITY, this.velocity.x);
- WriteCoord(MSG_ENTITY, this.velocity.y);
- WriteCoord(MSG_ENTITY, this.velocity.z);
+ WriteVector(MSG_ENTITY, this.velocity);
}
return true;
void Item_RespawnCountdown (entity this)
{
- if(this.count >= ITEM_RESPAWN_TICKS)
+ if(this.item_respawncounter >= ITEM_RESPAWN_TICKS)
{
if(this.waypointsprite_attached)
WaypointSprite_Kill(this.waypointsprite_attached);
else
{
this.nextthink = time + 1;
- this.count += 1;
- if(this.count == 1)
+ this.item_respawncounter += 1;
+ if(this.item_respawncounter == 1)
{
do {
{
});
WaypointSprite_Ping(this.waypointsprite_attached);
- //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.count);
+ //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.item_respawncounter);
}
}
}
setthink(e, Item_RespawnCountdown);
e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
e.scheduledrespawntime = e.nextthink + ITEM_RESPAWN_TICKS;
- e.count = 0;
+ e.item_respawncounter = 0;
if(Item_ItemsTime_Allow(e.itemdef) || (e.weapons & WEPSET_SUPERWEAPONS))
{
t = Item_ItemsTime_UpdateTime(e, e.scheduledrespawntime);
AUTOCVAR(g_pickup_respawntime_scaling_offset, float, 0.0, "Multiply respawn time by `reciprocal / (p + offset) + linear` where `p` is the current number of players, takes effect with 2 or more players present, `offset` offsets the curve left or right - the results are not intuitive and I recommend plotting the respawn time and the number of items per player to see what's happening");
AUTOCVAR(g_pickup_respawntime_scaling_linear, float, 1.0, "Multiply respawn time by `reciprocal / (p + offset) + linear` where `p` is the current number of players, takes effect with 2 or more players present, `linear` can be used to simply scale the respawn time linearly");
+/// Adjust respawn time according to the number of players.
float adjust_respawntime(float normal_respawntime) {
float r = autocvar_g_pickup_respawntime_scaling_reciprocal;
float o = autocvar_g_pickup_respawntime_scaling_offset;
//LOG_INFOF("item %s will respawn in %f", e.classname, adjusted_respawntime);
// range: adjusted_respawntime - respawntimejitter .. adjusted_respawntime + respawntimejitter
- float actual_time = adjusted_respawntime + crandom() * e.respawntimejitter;
- Item_ScheduleRespawnIn(e, actual_time);
+ float respawn_in = adjusted_respawntime + crandom() * e.respawntimejitter;
+ Item_ScheduleRespawnIn(e, respawn_in);
}
else // if respawntime is -1, this item does not respawn
Item_Show(e, -1);
}
+AUTOCVAR(g_pickup_respawntime_initial_random, int, 1,
+ "For items that don't start spawned: 0: spawn after their normal respawntime; 1: spawn after `random * respawntime` with the *same* random; 2: same as 1 but each item has separate random");
+
void Item_ScheduleInitialRespawn(entity e)
{
Item_Show(e, 0);
- Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : ITEM_RESPAWNTIME_INITIAL(e)));
+
+ float spawn_in;
+ if (autocvar_g_pickup_respawntime_initial_random == 0)
+ {
+ // range: respawntime .. respawntime + respawntimejitter
+ spawn_in = e.respawntime + random() * e.respawntimejitter;
+ }
+ else
+ {
+ float rnd;
+ if (autocvar_g_pickup_respawntime_initial_random == 1)
+ {
+ static float shared_random = 0;
+ // NOTE this code works only if items are scheduled at the same time (normal case)
+ // NOTE2 random() can't return exactly 1 so this check always work as intended
+ if (!shared_random || floor(time) > shared_random)
+ shared_random = floor(time) + random();
+ rnd = shared_random - floor(time);
+ }
+ else
+ rnd = random();
+
+ // range:
+ // if respawntime >= ITEM_RESPAWN_TICKS: ITEM_RESPAWN_TICKS .. respawntime + respawntimejitter
+ // else: 0 .. ITEM_RESPAWN_TICKS
+ // this is to prevent powerups spawning unexpectedly without waypoints
+ spawn_in = ITEM_RESPAWN_TICKS + rnd * (e.respawntime + e.respawntimejitter - ITEM_RESPAWN_TICKS);
+ }
+
+ Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : spawn_in));
}
void GiveRandomWeapons(entity receiver, int num_weapons, string weapon_names,
- float shells, float bullets, float rockets, float cells, float plasma)
+ entity ammo_entity)
{
if (num_weapons == 0)
{
return;
}
int num_potential_weapons = tokenize_console(weapon_names);
- for (int i = 0; i < num_weapons; ++i)
+ for (int give_attempt = 0; give_attempt < num_weapons; ++give_attempt)
{
RandomSelection_Init();
- for (int j = 0; j < num_potential_weapons; ++j)
+ for (int weapon_index = 0; weapon_index < num_potential_weapons;
+ ++weapon_index)
{
- string weapon = argv(j);
+ string weapon = argv(weapon_index);
FOREACH(Weapons, it != WEP_Null,
{
// Finding a weapon which player doesn't have.
{
continue;
}
- switch (RandomSelection_chosen_ent.ammo_type)
- {
- case (RESOURCE_SHELLS):
- {
- GiveResource(receiver, RESOURCE_SHELLS, shells);
- break;
- }
- case (RESOURCE_BULLETS):
- {
- GiveResource(receiver, RESOURCE_BULLETS, bullets);
- break;
- }
- case (RESOURCE_ROCKETS):
- {
- GiveResource(receiver, RESOURCE_ROCKETS, rockets);
- break;
- }
- case (RESOURCE_CELLS):
- {
- GiveResource(receiver, RESOURCE_CELLS, cells);
- break;
- }
- case (RESOURCE_PLASMA):
- {
- GiveResource(receiver, RESOURCE_PLASMA, plasma);
- break;
- }
- }
+ GiveResource(receiver, RandomSelection_chosen_ent.ammo_type,
+ GetResourceAmount(ammo_entity,
+ RandomSelection_chosen_ent.ammo_type));
}
}
player.superweapons_finished = max(player.superweapons_finished, time) + item.superweapons_finished;
}
-LABEL(skip)
-
// always eat teamed entities
if(item.team)
pickedup = true;
void Item_Touch(entity this, entity toucher)
{
-
// remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
- if (this.classname == "droppedweapon")
+ if (Item_IsLoot(this))
{
if (ITEM_TOUCH_NEEDKILL())
{
toucher = M_ARGV(1, entity);
- if (this.classname == "droppedweapon")
+ if (Item_IsExpiring(this))
{
this.strength_finished = max(0, this.strength_finished - time);
this.invincible_finished = max(0, this.invincible_finished - time);
this.superweapons_finished = max(0, this.superweapons_finished - time);
}
- entity it = this.itemdef;
- bool gave = ITEM_HANDLE(Pickup, it, this, toucher);
+ bool gave = ITEM_HANDLE(Pickup, this.itemdef, this, toucher);
if (!gave)
{
- if (this.classname == "droppedweapon")
+ if (Item_IsExpiring(this))
{
// undo what we did above
this.strength_finished += time;
LABEL(pickup)
- toucher.last_pickup = time;
+ STAT(LAST_PICKUP, toucher) = time;
Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
_sound (toucher, (this.itemdef.instanceOfPowerup ? CH_TRIGGER_SINGLE : CH_TRIGGER), (this.item_pickupsound ? this.item_pickupsound : Sound_fixpath(this.item_pickupsound_ent)), VOL_BASE, ATTEN_NORM);
- if (this.classname == "droppedweapon")
+ MUTATOR_CALLHOOK(ItemTouched, this, toucher);
+ if (wasfreed(this))
+ {
+ return;
+ }
+
+ if (Item_IsLoot(this))
+ {
delete(this);
- else if (this.spawnshieldtime)
+ return;
+ }
+ if (!this.spawnshieldtime)
{
- entity e;
- if(this.team)
+ return;
+ }
+ entity e;
+ if (this.team)
+ {
+ RandomSelection_Init();
+ IL_EACH(g_items, it.team == this.team,
{
- RandomSelection_Init();
- IL_EACH(g_items, it.team == this.team,
+ if (it.itemdef) // is a registered item
{
- if(it.itemdef) // is a registered item
- {
- Item_Show(it, -1);
- RandomSelection_AddEnt(it, it.cnt, 0);
- }
- });
- e = RandomSelection_chosen_ent;
- Item_Show(e, 1); // reset its state so it is visible (extra sendflags doesn't matter, this happens anyway)
- }
- else
- e = this;
- Item_ScheduleRespawn(e);
+ Item_Show(it, -1);
+ it.scheduledrespawntime = 0;
+ RandomSelection_AddEnt(it, it.cnt, 0);
+ }
+ });
+ e = RandomSelection_chosen_ent;
+ Item_Show(e, 1); // reset its state so it is visible (extra sendflags doesn't matter, this happens anyway)
}
+ else
+ e = this;
+ Item_ScheduleRespawn(e);
}
void Item_Reset(entity this)
Item_Show(this, !this.state);
setorigin(this, this.origin);
- if (this.classname != "droppedweapon")
+ if (Item_IsLoot(this))
{
- setthink(this, Item_Think);
- this.nextthink = time;
-
- if (this.waypointsprite_attached)
- WaypointSprite_Kill(this.waypointsprite_attached);
-
- if (this.itemdef.instanceOfPowerup || (this.weapons & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
- Item_ScheduleInitialRespawn(this);
+ return;
+ }
+ setthink(this, Item_Think);
+ this.nextthink = time;
+ if (this.waypointsprite_attached)
+ {
+ WaypointSprite_Kill(this.waypointsprite_attached);
+ }
+ if (this.itemdef.instanceOfPowerup || (this.weapons & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
+ {
+ Item_ScheduleInitialRespawn(this);
}
}
return rating;
}
-.int item_group;
-.int item_group_count;
float healtharmor_pickupevalfunc(entity player, entity item)
{
float c = 0;
return rating;
}
-void Item_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+void Item_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
if(ITEM_DAMAGE_NEEDKILL(deathtype))
RemoveItem(this);
return;
}
- // is it a dropped weapon?
- if (this.classname == "droppedweapon")
+ if (Item_IsLoot(this))
{
this.reset = SUB_Remove;
- // it's a dropped weapon
set_movetype(this, MOVETYPE_TOSS);
// Savage: remove thrown items after a certain period of time ("garbage collection")
this.takedamage = DAMAGE_YES;
this.event_damage = Item_Damage;
- if(this.strength_finished || this.invincible_finished || this.superweapons_finished)
+ if (Item_IsExpiring(this))
{
// if item is worthless after a timer, have it expire then
this.nextthink = max(this.strength_finished, this.invincible_finished, this.superweapons_finished);
if(def.instanceOfWeaponPickup)
{
- if (this.classname != "droppedweapon") // if dropped, colormap is already set up nicely
+ if (!Item_IsLoot(this)) // if dropped, colormap is already set up nicely
this.colormap = 1024; // color shirt=0 pants=0 grey
else
this.gravity = 1;
void StartItem(entity this, GameItem def)
{
+ def = def.m_spawnfunc_hookreplace(def, this);
+ if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
+ {
+ delete(this);
+ return;
+ }
+ this.classname = def.m_canonical_spawnfunc;
_StartItem(
this,
this.itemdef = def,
}
}
-spawnfunc(item_rockets)
-{
- StartItem(this, ITEM_Rockets);
-}
-
-spawnfunc(item_bullets)
-{
- if(!weaponswapping && autocvar_sv_q3acompat_machineshotgunswap &&
- (this.classname != "droppedweapon"))
- {
- weaponswapping = true;
- spawnfunc_item_shells(this);
- weaponswapping = false;
- return;
- }
-
- StartItem(this, ITEM_Bullets);
-}
-
-spawnfunc(item_cells)
-{
- StartItem(this, ITEM_Cells);
-}
-
-spawnfunc(item_plasma)
-{
- StartItem(this, ITEM_Plasma);
-}
-
-spawnfunc(item_shells)
-{
- if(!weaponswapping && autocvar_sv_q3acompat_machineshotgunswap &&
- (this.classname != "droppedweapon"))
- {
- weaponswapping = true;
- spawnfunc_item_bullets(this);
- weaponswapping = false;
- return;
- }
-
- StartItem(this, ITEM_Shells);
-}
-
-spawnfunc(item_armor_small)
-{
- StartItem(this, ITEM_ArmorSmall);
-}
-
-spawnfunc(item_armor_medium)
-{
- StartItem(this, ITEM_ArmorMedium);
-}
-
-spawnfunc(item_armor_big)
-{
- StartItem(this, ITEM_ArmorBig);
-}
-
-spawnfunc(item_armor_mega)
-{
- StartItem(this, ITEM_ArmorMega);
-}
-
-spawnfunc(item_health_small)
-{
- StartItem(this, ITEM_HealthSmall);
-}
-
-spawnfunc(item_health_medium)
-{
- StartItem(this, ITEM_HealthMedium);
-}
-
-spawnfunc(item_health_big)
-{
- StartItem(this, ITEM_HealthBig);
-}
-
-spawnfunc(item_health_mega)
-{
- StartItem(this, ITEM_HealthMega);
-}
-
-// support old misnamed entities
-spawnfunc(item_armor1) { spawnfunc_item_armor_small(this); } // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
-spawnfunc(item_armor25) { spawnfunc_item_armor_mega(this); }
-spawnfunc(item_armor_large) { spawnfunc_item_armor_mega(this); }
-spawnfunc(item_health1) { spawnfunc_item_health_small(this); }
-spawnfunc(item_health25) { spawnfunc_item_health_medium(this); }
-spawnfunc(item_health_large) { spawnfunc_item_health_big(this); }
-spawnfunc(item_health100) { spawnfunc_item_health_mega(this); }
-
-spawnfunc(item_strength)
-{
- StartItem(this, ITEM_Strength);
-}
-
-spawnfunc(item_invincible)
-{
- StartItem(this, ITEM_Shield);
-}
-
-// compatibility:
-spawnfunc(item_quad) { this.classname = "item_strength";spawnfunc_item_strength(this);}
-
void target_items_use(entity this, entity actor, entity trigger)
{
- if(actor.classname == "droppedweapon")
+ if(Item_IsLoot(actor))
{
EXACTTRIGGER_TOUCH(this, trigger);
delete(actor);
EXACTTRIGGER_TOUCH(this, trigger);
}
- IL_EACH(g_items, it.enemy == actor && it.classname == "droppedweapon",
+ IL_EACH(g_items, it.enemy == actor && Item_IsLoot(it),
{
delete(it);
});
s = Buff_UndeprecateName(argv(j));
if(s == it.m_name)
{
- this.buffs |= (it.m_itemid);
+ STAT(BUFFS, this) |= (it.m_itemid);
break;
}
});
if(this.ammo_fuel != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_fuel), "fuel");
if(this.health != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.health), "health");
if(this.armorvalue != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.armorvalue), "armor");
- FOREACH(Buffs, it != BUFF_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(this.buffs & (it.m_itemid)), it.m_name));
+ FOREACH(Buffs, it != BUFF_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(STAT(BUFFS, this) & (it.m_itemid)), it.m_name));
FOREACH(Weapons, it != WEP_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(this.weapons & (it.m_wepset)), it.netname));
}
this.netname = strzone(this.netname);
}
}
-spawnfunc(item_fuel)
-{
- StartItem(this, ITEM_JetpackFuel);
-}
-
-spawnfunc(item_fuel_regen)
-{
- if(start_items & ITEM_JetpackRegen.m_itemid)
- {
- spawnfunc_item_fuel(this);
- return;
- }
- StartItem(this, ITEM_JetpackRegen);
-}
-
-spawnfunc(item_jetpack)
-{
- if(start_items & ITEM_Jetpack.m_itemid)
- {
- spawnfunc_item_fuel(this);
- return;
- }
- StartItem(this, ITEM_Jetpack);
-}
-
float GiveWeapon(entity e, float wpn, float op, float val)
{
WepSet v0, v1;
bool GiveBuff(entity e, Buff thebuff, int op, int val)
{
- bool had_buff = (e.buffs & thebuff.m_itemid);
+ bool had_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
switch(op)
{
case OP_SET:
if(val > 0)
- e.buffs |= thebuff.m_itemid;
+ STAT(BUFFS, e) |= thebuff.m_itemid;
else
- e.buffs &= ~thebuff.m_itemid;
+ STAT(BUFFS, e) &= ~thebuff.m_itemid;
break;
case OP_MIN:
case OP_PLUS:
if(val > 0)
- e.buffs |= thebuff.m_itemid;
+ STAT(BUFFS, e) |= thebuff.m_itemid;
break;
case OP_MAX:
if(val <= 0)
- e.buffs &= ~thebuff.m_itemid;
+ STAT(BUFFS, e) &= ~thebuff.m_itemid;
break;
case OP_MINUS:
if(val > 0)
- e.buffs &= ~thebuff.m_itemid;
+ STAT(BUFFS, e) &= ~thebuff.m_itemid;
break;
}
- bool have_buff = (e.buffs & thebuff.m_itemid);
+ bool have_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
return (had_buff != have_buff);
}