X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fcommon%2Ftriggers%2Ftrigger%2Fjumppads.qc;h=9e40cfd40178224fc9c48efe76cece7de982253a;hp=498f0ff2dbbffb885c36a546ff7f4faba2bac378;hb=13e8382b4bd9c0de8bcfd2a96f56e638ba5d431d;hpb=1c9d0b4994d43c970e3fa02f651b5f5d79765e5b diff --git a/qcsrc/common/triggers/trigger/jumppads.qc b/qcsrc/common/triggers/trigger/jumppads.qc index 498f0ff2db..9e40cfd401 100644 --- a/qcsrc/common/triggers/trigger/jumppads.qc +++ b/qcsrc/common/triggers/trigger/jumppads.qc @@ -25,22 +25,20 @@ REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH) tgt - target entity (can be either a point or a model entity; if it is the latter, its midpoint is used) ht - jump height, measured from the higher one of org and tgt's midpoint + pushed_entity - object that is to be pushed Returns: velocity for the jump - the global trigger_push_calculatevelocity_flighttime is set to the total - jump time */ - -vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) +vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity) { float grav, sdist, zdist, vs, vz, jumpheight; vector sdir, torg; torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5; - grav = PHYS_GRAVITY(other); - if(PHYS_ENTGRAVITY(other)) - grav *= PHYS_ENTGRAVITY(other); + grav = PHYS_GRAVITY(NULL); + if(pushed_entity && PHYS_ENTGRAVITY(pushed_entity)) + grav *= PHYS_ENTGRAVITY(pushed_entity); zdist = torg.z - org.z; sdist = vlen(torg - org - zdist * '0 0 1'); @@ -88,6 +86,7 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) if(zdist == 0) solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually) + float flighttime; if(zdist < 0) { // down-jump @@ -96,14 +95,14 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) // almost straight line type // jump apex is before the jump // we must take the larger one - trigger_push_calculatevelocity_flighttime = solution.y; + flighttime = solution.y; } else { // regular jump // jump apex is during the jump // we must take the larger one too - trigger_push_calculatevelocity_flighttime = solution.y; + flighttime = solution.y; } } else @@ -114,39 +113,30 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) // almost straight line type // jump apex is after the jump // we must take the smaller one - trigger_push_calculatevelocity_flighttime = solution.x; + flighttime = solution.x; } else { // regular jump // jump apex is during the jump // we must take the larger one - trigger_push_calculatevelocity_flighttime = solution.y; + flighttime = solution.y; } } - vs = sdist / trigger_push_calculatevelocity_flighttime; + vs = sdist / flighttime; // finally calculate the velocity return sdir * vs + '0 0 1' * vz; } -void trigger_push_touch(entity this, entity toucher) +bool jumppad_push(entity this, entity targ) { - if (this.active == ACTIVE_NOT) - return; - - if (!isPushable(toucher)) - return; - - if(this.team) - if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, toucher))) - return; - - EXACTTRIGGER_TOUCH(this, toucher); + if (!isPushable(targ)) + return false; if(this.enemy) { - toucher.velocity = trigger_push_calculatevelocity(toucher.origin, this.enemy, this.height); + targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ); } else if(this.target && this.target != "") { @@ -159,105 +149,120 @@ void trigger_push_touch(entity this, entity toucher) else RandomSelection_AddEnt(e, 1, 1); } - toucher.velocity = trigger_push_calculatevelocity(toucher.origin, RandomSelection_chosen_ent, this.height); + targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ); } else { - toucher.velocity = this.movedir; + targ.velocity = this.movedir; } - UNSET_ONGROUND(toucher); + UNSET_ONGROUND(targ); #ifdef CSQC - if (toucher.flags & FL_PROJECTILE) + if (targ.flags & FL_PROJECTILE) { - toucher.angles = vectoangles (toucher.velocity); - switch(toucher.move_movetype) + targ.angles = vectoangles (targ.velocity); + switch(targ.move_movetype) { case MOVETYPE_FLY: - set_movetype(toucher, MOVETYPE_TOSS); - toucher.gravity = 1; + set_movetype(targ, MOVETYPE_TOSS); + targ.gravity = 1; break; case MOVETYPE_BOUNCEMISSILE: - set_movetype(toucher, MOVETYPE_BOUNCE); - toucher.gravity = 1; + set_movetype(targ, MOVETYPE_BOUNCE); + targ.gravity = 1; break; } } #endif #ifdef SVQC - if (IS_PLAYER(toucher)) + if (IS_PLAYER(targ)) { // reset tracking of oldvelocity for impact damage (sudden velocity changes) - toucher.oldvelocity = toucher.velocity; + targ.oldvelocity = targ.velocity; if(this.pushltime < time) // prevent "snorring" sound when a player hits the jumppad more than once { // flash when activated - Send_Effect(EFFECT_JUMPPAD, toucher.origin, toucher.velocity, 1); - _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_JUMPPAD, targ.origin, targ.velocity, 1); + _sound (targ, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); this.pushltime = time + 0.2; } - if(IS_REAL_CLIENT(toucher) || IS_BOT_CLIENT(toucher)) + if(IS_REAL_CLIENT(targ) || IS_BOT_CLIENT(targ)) { bool found = false; - for(int i = 0; i < toucher.jumppadcount && i < NUM_JUMPPADSUSED; ++i) - if(toucher.(jumppadsused[i]) == this) + for(int i = 0; i < targ.jumppadcount && i < NUM_JUMPPADSUSED; ++i) + if(targ.(jumppadsused[i]) == this) found = true; if(!found) { - toucher.(jumppadsused[toucher.jumppadcount % NUM_JUMPPADSUSED]) = this; - toucher.jumppadcount = toucher.jumppadcount + 1; + targ.(jumppadsused[targ.jumppadcount % NUM_JUMPPADSUSED]) = this; + targ.jumppadcount = targ.jumppadcount + 1; } - if(IS_REAL_CLIENT(toucher)) + if(IS_REAL_CLIENT(targ)) { if(this.message) - centerprint(toucher, this.message); + centerprint(targ, this.message); } else - toucher.lastteleporttime = time; + { + targ.lastteleporttime = time; + targ.lastteleport_origin = targ.origin; + } - if (!IS_DEAD(toucher)) - animdecide_setaction(toucher, ANIMACTION_JUMP, true); + if (!IS_DEAD(targ)) + animdecide_setaction(targ, ANIMACTION_JUMP, true); } else - toucher.jumppadcount = true; + targ.jumppadcount = 1; // reset tracking of who pushed you into a hazard (for kill credit) - toucher.pushltime = 0; - toucher.istypefrag = 0; + targ.pushltime = 0; + targ.istypefrag = 0; } if(this.enemy.target) - SUB_UseTargets(this.enemy, toucher, toucher); // TODO: do we need toucher as trigger too? + SUB_UseTargets(this.enemy, targ, this); - if (toucher.flags & FL_PROJECTILE) + if (targ.flags & FL_PROJECTILE) { - toucher.angles = vectoangles (toucher.velocity); - toucher.com_phys_gravity_factor = 1; - switch(toucher.move_movetype) + targ.angles = vectoangles (targ.velocity); + targ.com_phys_gravity_factor = 1; + switch(targ.move_movetype) { case MOVETYPE_FLY: - set_movetype(toucher, MOVETYPE_TOSS); - toucher.gravity = 1; + set_movetype(targ, MOVETYPE_TOSS); + targ.gravity = 1; break; case MOVETYPE_BOUNCEMISSILE: - set_movetype(toucher, MOVETYPE_BOUNCE); - toucher.gravity = 1; + set_movetype(targ, MOVETYPE_BOUNCE); + targ.gravity = 1; break; } - UpdateCSQCProjectile(toucher); + UpdateCSQCProjectile(targ); } +#endif - /*if (toucher.flags & FL_ITEM) - { - ItemUpdate(toucher); - toucher.SendFlags |= ISF_DROP; - }*/ + return true; +} - if (this.spawnflags & PUSH_ONCE) +void trigger_push_touch(entity this, entity toucher) +{ + if (this.active == ACTIVE_NOT) + return; + + if(this.team) + if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, toucher))) + return; + + EXACTTRIGGER_TOUCH(this, toucher); + + noref bool success = jumppad_push(this, toucher); + +#ifdef SVQC + if (success && (this.spawnflags & PUSH_ONCE)) { settouch(this, func_null); setthink(this, SUB_Remove); @@ -269,41 +274,152 @@ void trigger_push_touch(entity this, entity toucher) #ifdef SVQC void trigger_push_link(entity this); void trigger_push_updatelink(entity this); -#endif -void trigger_push_findtarget(entity this) +bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org) { - entity t; - vector org; + setorigin(tracetest_ent, org); + tracetoss(tracetest_ent, tracetest_ent); + if(trace_startsolid) + return false; + if (!jp.height) + { + // since tracetoss starting from jumppad's origin often fails when target + // is very close to real destination, start it directly from target's + // origin instead + vector ofs = '0 0 0'; + if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed)) + ofs = stepheightvec; + + tracetest_ent.velocity.z = 0; + setorigin(tracetest_ent, targ.origin + ofs); + tracetoss(tracetest_ent, tracetest_ent); + if (trace_startsolid && ofs.z) + { + setorigin(tracetest_ent, targ.origin + ofs / 2); + tracetoss(tracetest_ent, tracetest_ent); + if (trace_startsolid && ofs.z) + { + setorigin(tracetest_ent, targ.origin); + tracetoss(tracetest_ent, tracetest_ent); + if (trace_startsolid) + return false; + } + } + } + tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent); + return true; +} +#endif + +/// if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise +/// if (item == NULL) tests jumppad's trajectory and eventually spawns waypoints for it (return value doesn't matter) +bool trigger_push_test(entity this, entity item) +{ // first calculate a typical start point for the jump - org = (this.absmin + this.absmax) * 0.5; - org_z = this.absmax.z - STAT(PL_MIN, NULL).z; + vector org = (this.absmin + this.absmax) * 0.5; + org.z = this.absmax.z - PL_MIN_CONST.z - 10; if (this.target) { - float n = 0; - for(t = NULL; (t = find(t, targetname, this.target)); ) + int n = 0; +#ifdef SVQC + vector vel = '0 0 0'; +#endif + for(entity t = NULL; (t = find(t, targetname, this.target)); ) { ++n; #ifdef SVQC + if(t.move_movetype != MOVETYPE_NONE) + continue; + entity e = spawn(); - setorigin(e, org); - setsize(e, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL)); - e.velocity = trigger_push_calculatevelocity(org, t, this.height); - tracetoss(e, e); - if(e.move_movetype == MOVETYPE_NONE) - waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity)); + setsize(e, PL_MIN_CONST, PL_MAX_CONST); + e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + e.velocity = trigger_push_calculatevelocity(org, t, this.height, e); + + if(item) + { + setorigin(e, org); + tracetoss(e, e); + bool r = (trace_ent == item); + delete(e); + return r; + } + + vel = e.velocity; + vector best_target = '0 0 0'; + vector best_org = '0 0 0'; + vector best_vel = '0 0 0'; + bool valid_best_target = false; + if (trigger_push_testorigin(e, t, this, org)) + { + best_target = trace_endpos; + best_org = org; + best_vel = e.velocity; + valid_best_target = true; + } + + vector new_org; + vector dist = t.origin - org; + if (dist.x || dist.y) // if not perfectly vertical + { + // test trajectory with different starting points, sometimes the trajectory + // starting from the jumppad origin can't reach the real destination + // and destination waypoint ends up near the jumppad itself + vector flatdir = normalize(dist - eZ * dist.z); + vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y)); + new_org = org + ofs; + e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e); + vel = e.velocity; + if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed)) + e.velocity = autocvar_sv_maxspeed * flatdir; + if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50)) + { + best_target = trace_endpos; + best_org = new_org; + best_vel = vel; + valid_best_target = true; + } + new_org = org - ofs; + e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e); + vel = e.velocity; + if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed)) + e.velocity = autocvar_sv_maxspeed * flatdir; + if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50)) + { + best_target = trace_endpos; + best_org = new_org; + best_vel = vel; + valid_best_target = true; + } + } + + if (valid_best_target) + { + if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST))) + { + float velxy = vlen(vec2(best_vel)); + float cost = vlen(vec2(t.origin - best_org)) / velxy; + if(velxy < autocvar_sv_maxspeed) + velxy = autocvar_sv_maxspeed; + cost += vlen(vec2(best_target - t.origin)) / velxy; + waypoint_spawnforteleporter(this, best_target, cost, e); + } + } delete(e); #endif } + if(item) + return false; + if(!n) { // no dest! #ifdef SVQC objerror (this, "Jumppad with nonexistant target"); #endif - return; + return false; } else if(n == 1) { @@ -320,17 +436,30 @@ void trigger_push_findtarget(entity this) else { entity e = spawn(); + setsize(e, PL_MIN_CONST, PL_MAX_CONST); + e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; setorigin(e, org); - setsize(e, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL)); e.velocity = this.movedir; tracetoss(e, e); - waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity)); + if(item) + { + bool r = (trace_ent == item); + delete(e); + return r; + } + if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, trace_endpos + PL_MIN_CONST, trace_endpos + PL_MAX_CONST))) + waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity), e); delete(e); } - trigger_push_link(this); defer(this, 0.1, trigger_push_updatelink); #endif + return true; +} + +void trigger_push_findtarget(entity this) +{ + trigger_push_test(this, NULL); } #ifdef SVQC @@ -393,6 +522,10 @@ spawnfunc(trigger_push) this.noise = "misc/jumppad.wav"; precache_sound (this.noise); + trigger_push_link(this); // link it now + + IL_PUSH(g_jumppads, this); + // this must be called to spawn the teleport waypoints for bots InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET); } @@ -415,6 +548,14 @@ bool target_push_send(entity this, entity to, float sf) return true; } +void target_push_use(entity this, entity actor, entity trigger) +{ + if(trigger.classname == "trigger_push" || trigger == this) + return; // WTF, why is this a thing + + jumppad_push(this, actor); +} + void target_push_link(entity this) { BITSET_ASSIGN(this.effects, EF_NODEPTHTEST); @@ -429,7 +570,18 @@ void target_push_init(entity this) target_push_link(this); } -spawnfunc(target_push) { target_push_init(this); } +void target_push_init2(entity this) +{ + if(this.target && this.target != "") // we have an old style pusher! + { + InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET); + this.use = target_push_use; + } + + target_push_init(this); // normal push target behaviour can be combined with a legacy pusher? +} + +spawnfunc(target_push) { target_push_init2(this); } spawnfunc(info_notnull) { target_push_init(this); } spawnfunc(target_position) { target_push_init(this); } @@ -460,9 +612,9 @@ NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, bool isnew) void target_push_remove(entity this) { - if(this.classname) - strunzone(this.classname); - this.classname = string_null; + //if(this.classname) + //strunzone(this.classname); + //this.classname = string_null; if(this.targetname) strunzone(this.targetname);