X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fclient%2Fitems%2Fitems.qc;h=755f0f9cd40d0a52aff03018e70a485ef9574d17;hb=ffacefe00ac83d2fd630268ff1e2d56854883876;hp=72f69ee07f21f6a3296076686e5346d58aa589e9;hpb=c3e3dd71484d563af474cb4b9e8624b6093e0b0f;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/client/items/items.qc b/qcsrc/client/items/items.qc index 72f69ee07..755f0f9cd 100644 --- a/qcsrc/client/items/items.qc +++ b/qcsrc/client/items/items.qc @@ -1,273 +1,331 @@ #include "items.qh" -#include - #include +#include #include #include #include #include #include -bool autocvar_cl_ghost_items_vehicle = true; .vector item_glowmod; -.bool item_simple; // probably not really needed, but better safe than sorry +.int item_simple; .float alpha; -void Item_SetAlpha(entity this) +.bool pushable; +.float anim_start_time; // reusing for bob waveform synchronisation +.vector angles_held; // reusing for (re)storing original angles +.float wait, delay, pointtime; // reusing for despawn effects + +HashMap ENT_CLIENT_ITEM_simple; +STATIC_INIT(ENT_CLIENT_ITEM_simple) { - bool veh_hud = (hud && autocvar_cl_ghost_items_vehicle); + HM_NEW(ENT_CLIENT_ITEM_simple); +} +SHUTDOWN(ENT_CLIENT_ITEM_simple) +{ + HM_DELETE(ENT_CLIENT_ITEM_simple); +} - if(!veh_hud && (this.ItemStatus & ITS_AVAILABLE)) +void ItemSetModel(entity this, bool wantsimple) +{ + if(wantsimple) { - this.alpha = 1; - this.colormod = '1 1 1'; - this.glowmod = this.item_glowmod; + string _fn2 = substring(this.mdl, 0 , strlen(this.mdl) -4); + #define extensions(x) \ + x(iqm) \ + x(dpm) \ + x(md3) \ + x(mdl) \ + /**/ + #define tryext(ext) { \ + string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \ + string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \ + if (cached == "") { \ + HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \ + } \ + if (cached != "0") { \ + this.model = s; \ + this.item_simple = 1; \ + break; \ + } \ + } + do { + extensions(tryext); + this.model = this.mdl; // fall back to 3d model + this.item_simple = -1; // don't retry every frame + LOG_TRACEF("Simple item requested for %s but no model exists for it", this.mdl); + } while (0); + #undef tryext + #undef extensions } else { - this.alpha = autocvar_cl_ghost_items; - this.colormod = this.glowmod = autocvar_cl_ghost_items_color; - } - - if((!veh_hud) && (this.ItemStatus & ITS_STAYWEP)) - { - this.colormod = this.glowmod = autocvar_cl_weapon_stay_color; - this.alpha = autocvar_cl_weapon_stay_alpha; + this.model = this.mdl; + this.item_simple = 0; } - this.drawmask = ((this.alpha <= 0) ? 0 : MASK_NORMAL); + // this.model is an engine string so it doesn't need to be zoned and can't be unzoned + if(this.model == "") + LOG_WARNF("this.model is unset for item %s", this.classname); + precache_model(this.model); + _setmodel(this, this.model); + setsize(this, '-16 -16 0', '16 16 48'); + // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0 } void ItemDraw(entity this) { - if(this.gravity) - { - Movetype_Physics_MatchServer(this, false); - if(IS_ONGROUND(this)) - { // For some reason avelocity gets set to '0 0 0' here ... - this.oldorigin = this.origin; - this.gravity = 0; - - if(autocvar_cl_animate_items) - { // ... so reset it if animations are requested. - if(this.ItemStatus & ITS_ANIMATE1) - this.avelocity = '0 180 0'; - - if(this.ItemStatus & ITS_ANIMATE2) - this.avelocity = '0 -90 0'; - } - - // delay is for blocking item's position for a while; - // it's a workaround for dropped weapons that receive the position - // another time right after they spawn overriding animation position - this.onground_time = time + 0.5; - } - } - else if (autocvar_cl_animate_items && !this.item_simple) // no bobbing applied to simple items, for consistency's sake (no visual difference between ammo and weapons) - { - if(this.ItemStatus & ITS_ANIMATE1) - { - 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; - 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); -} + bool wantsimple = autocvar_cl_simple_items && this.ItemStatus & ITS_ALLOWSI; + if(wantsimple != this.item_simple && this.item_simple != -1) + ItemSetModel(this, wantsimple); -void Item_PreDraw(entity this) -{ - if(warpzone_warpzones_exist) + // no bobbing applied to simple items, for consistency's sake (no visual difference between ammo and weapons) + bool animate = autocvar_cl_items_animate & 1 && this.item_simple <= 0 && (this.ItemStatus & ITS_ANIMATE1 || this.ItemStatus & ITS_ANIMATE2); + + // rotation must be set before running physics + if(!animate) { - setpredraw(this, func_null); // no need to keep running this - return; + this.avelocity_y = 0; + this.angles = this.angles_held; // restore angles sent from server + } + else if(!this.avelocity_y) // unset by MOVETYPE_TOSS or animation was disabled previously + { + if(this.ItemStatus & ITS_ANIMATE1) + this.avelocity_y = 180; + else if(this.ItemStatus & ITS_ANIMATE2) + this.avelocity_y = -90; + } + + // CSQC physics OR bobbing (both would look weird) + float bobheight = 0; // reset bob offset if animations are disabled + if(this.move_movetype && (!IS_ONGROUND(this) || this.velocity != '0 0 0')) + { + // this isn't equivalent to player prediction but allows smooth motion with very low ISF_LOCATION rate + // which requires running this even if the item is just outside visible range (it could be moving into range) + if(animate) + bobheight = this.origin_z - this.oldorigin_z; + Movetype_Physics_NoMatchTicrate(this, frametime, true); + this.oldorigin = this.origin; // update real (SVQC equivalent) origin + if(animate) + { + if(bobheight) + { + this.anim_start_time += frametime; // bobbing is paused this frame + this.oldorigin_z -= bobheight; // restore bob offset (CSQC physics uses the offset bbox) + } + else + { + this.anim_start_time = time; // starting our bob animation from NOW + if(this.ItemStatus & ITS_ANIMATE1) + bobheight = 10; // height of wave at 0 time + else if(this.ItemStatus & ITS_ANIMATE2) + bobheight = 8; // height of wave at 0 time + } + } + } + else if(animate) + { + this.angles += this.avelocity * frametime; // MOVETYPE_TOSS does this while it's moving + + if(this.ItemStatus & ITS_ANIMATE1) + bobheight = 10 + 8 * sin((time - this.anim_start_time) * 2); + else if(this.ItemStatus & ITS_ANIMATE2) + bobheight = 8 + 4 * sin((time - this.anim_start_time) * 3); + } + + // apply new bob offset + if (bobheight != this.origin_z - this.oldorigin_z) + { + this.origin_z = this.oldorigin_z + bobheight; + this.mins_z = 0 - bobheight; // don't want the absmin and absmax to bob + this.maxs_z = 48 - bobheight; + // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0 } - float alph; - vector org = getpropertyvec(VF_ORIGIN); - //if(!checkpvs(org, this)) // this makes sense as long as we don't support recursive warpzones - //alph = 0; // this shouldn't be needed, since items behind walls are culled anyway - if(this.fade_start) + + // set alpha based on distance + this.alpha = 1; + this.drawmask = 0; + if(this.fade_end && !warpzone_warpzones_exist) { + vector org = getpropertyvec(VF_ORIGIN); if(vdist(org - this.origin, >, this.fade_end)) - alph = 0; // save on some processing - else if(vdist(org - this.origin, <, this.fade_start)) - alph = 1; // more processing saved + this.alpha = 0; // save on some processing + else if(autocvar_cl_items_fadedist > 0) + { + this.fade_start = max(500, this.fade_end - autocvar_cl_items_fadedist); + if(vdist(org - this.origin, >, this.fade_start)) + this.alpha = bound(0, (this.fade_end - vlen(org - this.origin - 0.5 * (this.mins + this.maxs))) / (this.fade_end - this.fade_start), 1); + } + } + + if(!this.alpha) + return; + + // modify alpha based on availability and vehicle hud + if(this.ItemStatus & ITS_AVAILABLE) + { + if(hud) // apparently this means we're in a vehicle lol + { + this.alpha *= autocvar_cl_items_vehicle_alpha; + this.colormod = this.glowmod = autocvar_cl_items_vehicle_color; + } + else if(this.ItemStatus & ITS_STAYWEP) + { + this.alpha *= autocvar_cl_weapon_stay_alpha; + this.colormod = this.glowmod = autocvar_cl_weapon_stay_color; + } else - alph = bound(0, (this.fade_end - vlen(org - this.origin - 0.5 * (this.mins + this.maxs))) / (this.fade_end - this.fade_start), 1); + { + this.colormod = '1 1 1'; + this.glowmod = this.item_glowmod; + } } else - alph = 1; - //printf("%v <-> %v\n", view_origin, this.origin + 0.5 * (this.mins + this.maxs)); - if(!hud && (this.ItemStatus & ITS_AVAILABLE)) - this.alpha = alph; - if(alph <= 0) - this.drawmask = 0; - //else - //this.drawmask = MASK_NORMAL; // reset by the setalpha function + { + this.alpha *= autocvar_cl_ghost_items; + this.colormod = this.glowmod = autocvar_cl_ghost_items_color; + } + + if(!this.alpha) + return; + + // loot item despawn effects + if(this.ItemStatus & ITS_EXPIRING) + { + if(!this.wait) // when receiving the first message with ITS_EXPIRING set + { + this.wait = time + IT_DESPAWNFX_TIME; // it will despawn then + this.delay = 0.25; + } + + if(autocvar_cl_items_animate & 2) + this.alpha *= (this.wait - time) / IT_DESPAWNFX_TIME; + + if(autocvar_cl_items_animate & 4 && time >= this.pointtime) + { + pointparticles(EFFECT_ITEM_DESPAWN, this.origin + '0 0 16', '0 0 0', 1); + if (this.delay > 0.0625) + this.delay *= 0.5; + this.pointtime = time + this.delay; + } + } + + this.drawmask = this.alpha <= 0 ? 0 : MASK_NORMAL; } void ItemRemove(entity this) { + if(this.alpha) + if(!this.wait || time < this.wait - ticrate) // despawning loot items have their own effects + pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1); strfree(this.mdl); } -HashMap ENT_CLIENT_ITEM_simple; -STATIC_INIT(ENT_CLIENT_ITEM_simple) -{ - HM_NEW(ENT_CLIENT_ITEM_simple); -} -SHUTDOWN(ENT_CLIENT_ITEM_simple) -{ - HM_DELETE(ENT_CLIENT_ITEM_simple); -} - NET_HANDLE(ENT_CLIENT_ITEM, bool isnew) { - int sf = ReadByte(); - - if(sf & ISF_LOCATION) - { - this.origin = ReadVector(); - setorigin(this, this.origin); - this.oldorigin = this.origin; - } - - if(sf & ISF_ANGLES) - { - this.angles = ReadAngleVector(); - } - - if(sf & ISF_SIZE) - { - setsize(this, '-16 -16 0', '16 16 48'); - } - - if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc. - { - this.ItemStatus = ReadByte(); - - Item_SetAlpha(this); - - if(this.ItemStatus & ITS_ALLOWFB) - this.effects |= EF_FULLBRIGHT; - else - this.effects &= ~EF_FULLBRIGHT; - - if(this.ItemStatus & ITS_GLOW) - { - if(this.ItemStatus & ITS_AVAILABLE) - this.effects |= (EF_ADDITIVE | EF_FULLBRIGHT); - else - this.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT); - } - } - - if(sf & ISF_MODEL) - { - this.drawmask = MASK_NORMAL; - set_movetype(this, MOVETYPE_TOSS); - if (isnew) IL_PUSH(g_drawables, this); - this.draw = ItemDraw; - this.solid = SOLID_TRIGGER; - //this.flags |= FL_ITEM; - - this.fade_end = ReadShort(); - this.fade_start = ReadShort(); - if(!warpzone_warpzones_exist && this.fade_start && !autocvar_cl_items_nofade) - setpredraw(this, Item_PreDraw); - - strfree(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.item_simple = true; - - #define extensions(x) \ - x(md3) \ - x(dpm) \ - x(iqm) \ - x(mdl) \ - /**/ - #define tryext(ext) { \ - string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \ - string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \ - if (cached == "") { \ - HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \ - } \ - if (cached != "0") { \ - strcpy(this.mdl, s); \ - break; \ - } \ + int sf = ReadByte(); + + if(sf & ISF_LOCATION) + { + float bobheight = this.origin_z - this.oldorigin_z; + this.origin = this.oldorigin = ReadVector(); + this.origin_z += bobheight; // restore animation offset (SVQC physics is unaware of CSQC bbox offset) + setorigin(this, this.origin); // link + } + + if(sf & ISF_ANGLES) + { + this.angles = this.angles_held = ReadAngleVector(); + } + + /* bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0 + if(sf & ISF_SIZE) + { + setsize(this, '-16 -16 0', '16 16 48'); + } + */ + + if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc. + { + int prevItemStatus = this.ItemStatus; + this.ItemStatus = ReadByte(); + + if(this.ItemStatus & ITS_ALLOWFB) + this.effects |= EF_FULLBRIGHT; + else + this.effects &= ~EF_FULLBRIGHT; + + if(this.ItemStatus & ITS_AVAILABLE) + { + if(this.solid != SOLID_TRIGGER) + { + this.solid = SOLID_TRIGGER; + setorigin(this, this.origin); // link it to the area grid + } + + if(this.ItemStatus & ITS_GLOW) + this.effects |= (EF_ADDITIVE | EF_FULLBRIGHT); + if(!(prevItemStatus & ITS_AVAILABLE) && this.alpha && !isnew) + pointparticles(EFFECT_ITEM_RESPAWN, (this.absmin + this.absmax) * 0.5, '0 0 0', 1); + } + else + { + if(this.solid != SOLID_NOT) + { + this.solid = SOLID_NOT; + setorigin(this, this.origin); // optimisation: unlink it from the area grid } - do { - extensions(tryext); - this.item_simple = false; - LOG_TRACEF("Simple item requested for %s but no model exists for it", _fn); - } while (0); - #undef tryext - #undef extensions - } - - if(!this.item_simple) - strcpy(this.mdl, _fn); - - if(this.mdl == "") - LOG_WARNF("this.mdl is unset for item %s", this.classname); - - precache_model(this.mdl); - _setmodel(this, this.mdl); - - setsize(this, '-16 -16 0', '16 16 48'); - } - - if(sf & ISF_COLORMAP) - { - this.colormap = ReadShort(); - this.item_glowmod_x = ReadByte() / 255.0; - this.item_glowmod_y = ReadByte() / 255.0; - this.item_glowmod_z = ReadByte() / 255.0; - } - - if(sf & ISF_DROP) - { - this.gravity = 1; - this.pushable = true; - //this.angles = '0 0 0'; - set_movetype(this, MOVETYPE_TOSS); - this.velocity = ReadVector(); - setorigin(this, this.oldorigin); - - if(!this.move_time) - { - this.move_time = time; - this.spawntime = time; - } - else - this.move_time = max(this.move_time, time); - } - - if(autocvar_cl_animate_items) - { - if(this.ItemStatus & ITS_ANIMATE1) - this.avelocity = '0 180 0'; - - if(this.ItemStatus & ITS_ANIMATE2) - this.avelocity = '0 -90 0'; - } - - this.entremove = ItemRemove; - - return true; + + if(this.ItemStatus & ITS_GLOW) + this.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT); + if(prevItemStatus & ITS_AVAILABLE && this.alpha) + pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1); + } + } + + if(sf & ISF_MODEL) + { + if(isnew) + { + IL_PUSH(g_drawables, this); + this.draw = ItemDraw; + this.flags |= FL_ITEM; + } + + this.fade_end = ReadShort(); + + strcpy(this.mdl, ReadString()); + this.item_simple = -2; + + this.skin = ReadByte(); + } + + if(sf & ISF_COLORMAP) + { + this.colormap = ReadShort(); + this.item_glowmod_x = ReadByte() / 255.0; + this.item_glowmod_y = ReadByte() / 255.0; + this.item_glowmod_z = ReadByte() / 255.0; + } + + if(sf & ISF_DROP) + { + this.gravity = 1; + this.pushable = true; + set_movetype(this, MOVETYPE_TOSS); + this.velocity = ReadVector(); + } + else if (this.gravity) // caution: kludge FIXME (with sv_legacy_bbox_expand) + { + // workaround for prediction errors caused by bbox discrepancy between SVQC and CSQC + this.gravity = 0; // don't do this kludge again + this.pushable = false; // no fun allowed + set_movetype(this, MOVETYPE_NONE); // disable physics + this.velocity = '0 0 0'; // disable it more + SET_ONGROUND(this); // extra overkill + } + + this.entremove = ItemRemove; + + return true; }