X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fcommon%2Ft_items.qc;h=02a0dc171cc34723e95e7671fde5504b80da47e0;hb=cac7a0ffc2bb7212fdd92f5c9782b09b65279fe1;hp=70c462288637bfa0af4f90f178afaf113626443a;hpb=3ae581bcb90fdf4a24324c4526124a52ce7e5e4c;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/common/t_items.qc b/qcsrc/common/t_items.qc index 70c462288..02a0dc171 100644 --- a/qcsrc/common/t_items.qc +++ b/qcsrc/common/t_items.qc @@ -37,6 +37,7 @@ REGISTER_NET_LINKED(ENT_CLIENT_ITEM) #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); @@ -96,14 +97,16 @@ void ItemDraw(entity this) { 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))); } @@ -112,19 +115,6 @@ void ItemDraw(entity this) 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) @@ -168,9 +158,7 @@ NET_HANDLE(ENT_CLIENT_ITEM, bool isnew) 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; } @@ -228,11 +216,12 @@ NET_HANDLE(ENT_CLIENT_ITEM, bool isnew) 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")); @@ -244,12 +233,12 @@ NET_HANDLE(ENT_CLIENT_ITEM, bool isnew) 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); @@ -276,9 +265,7 @@ NET_HANDLE(ENT_CLIENT_ITEM, bool isnew) 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) @@ -320,9 +307,7 @@ bool ItemSend(entity this, entity to, int sf) //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) @@ -365,9 +350,7 @@ bool ItemSend(entity this, entity to, int sf) 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; @@ -533,7 +516,7 @@ void Item_Respawn (entity this) 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); @@ -542,8 +525,8 @@ void Item_RespawnCountdown (entity this) else { this.nextthink = time + 1; - this.count += 1; - if(this.count == 1) + this.item_respawncounter += 1; + if(this.item_respawncounter == 1) { do { { @@ -584,7 +567,7 @@ void Item_RespawnCountdown (entity this) }); WaypointSprite_Ping(this.waypointsprite_attached); - //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.count); + //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.item_respawncounter); } } } @@ -607,7 +590,7 @@ void Item_ScheduleRespawnIn(entity e, float t) 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); @@ -635,6 +618,7 @@ AUTOCVAR(g_pickup_respawntime_scaling_reciprocal, float, 0.0, "Multiply respawn 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; @@ -669,17 +653,49 @@ void Item_ScheduleRespawn(entity e) //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, @@ -842,8 +858,6 @@ float Item_GiveTo(entity item, entity player) player.superweapons_finished = max(player.superweapons_finished, time) + item.superweapons_finished; } -LABEL(skip) - // always eat teamed entities if(item.team) pickedup = true; @@ -905,32 +919,28 @@ void Item_Touch(entity this, entity toucher) toucher = M_ARGV(1, entity); - // TODO: Proper way to handle expiring and not expiring loot. - // Expiring loot will have strength "ticking" will it's dropped. - // Not expiring will not tick. - // OTOH, do we really need expiring loot? - //if (this.classname == "droppedweapon") - //{ - // 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); - //} + 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); + } bool gave = ITEM_HANDLE(Pickup, this.itemdef, this, toucher); if (!gave) { - //if (this.classname == "droppedweapon") - //{ - // // undo what we did above - // this.strength_finished += time; - // this.invincible_finished += time; - // this.superweapons_finished += time; - //} + if (Item_IsExpiring(this)) + { + // undo what we did above + this.strength_finished += time; + this.invincible_finished += time; + this.superweapons_finished += time; + } return; } 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); @@ -1132,8 +1142,6 @@ float ammo_pickupevalfunc(entity player, entity item) return rating; } -.int item_group; -.int item_group_count; float healtharmor_pickupevalfunc(entity player, entity item) { float c = 0; @@ -1158,7 +1166,7 @@ float healtharmor_pickupevalfunc(entity player, entity item) 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); @@ -1232,11 +1240,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default this.takedamage = DAMAGE_YES; this.event_damage = Item_Damage; - // TODO: Proper way to handle expiring and not expiring loot. - // Expiring loot will have strength "ticking" will it's dropped. - // Not expiring will not tick. - // OTOH, do we really need expiring loot? - 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); @@ -1351,7 +1355,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default 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; @@ -1452,7 +1456,7 @@ void setItemGroupCount() void target_items_use(entity this, entity actor, entity trigger) { - if(actor.classname == "droppedweapon") + if(Item_IsLoot(actor)) { EXACTTRIGGER_TOUCH(this, trigger); delete(actor); @@ -1467,7 +1471,7 @@ void target_items_use(entity this, entity actor, entity trigger) 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); }); @@ -1513,7 +1517,7 @@ spawnfunc(target_items) s = Buff_UndeprecateName(argv(j)); if(s == it.m_name) { - this.buffs |= (it.m_itemid); + STAT(BUFFS, this) |= (it.m_itemid); break; } }); @@ -1573,7 +1577,7 @@ spawnfunc(target_items) 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); @@ -1622,30 +1626,30 @@ float GiveWeapon(entity e, float wpn, float op, float val) 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); }