void depot_give_inventory (edict_t *self, edict_t *other) { int result = 0; if ((self->sentrydelay > level.time) || !G_EntIsAlive(self) || !G_EntIsAlive(other) || !other->client || OnSameTeam(self, other) < 2) return; result += depot_give_item(self, other, body_armor_index); result += depot_give_item(self, other, bullet_index); result += depot_give_item(self, other, cell_index); result += depot_give_item(self, other, shell_index); result += depot_give_item(self, other, grenade_index); result += depot_give_item(self, other, rocket_index); result += depot_give_item(self, other, slug_index); safe_cprintf(other, PRINT_HIGH, "Depot has %d armor, %d bullets, %d cells, %d shells, %d grenades, %d rockets, %d slugs\n", self->packitems[body_armor_index], self->packitems[bullet_index], self->packitems[cell_index], self->packitems[shell_index], self->packitems[grenade_index], self->packitems[rocket_index], self->packitems[slug_index]); // delay before depot can be used again self->sentrydelay = level.time + 2.0; if (result > 0) gi.sound(self, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_STATIC, 0); }
void plague_think (edict_t *self) { int dmg; float radius; edict_t *e=NULL; // plague self-terminates if: if (!G_EntIsAlive(self->owner) || !G_EntIsAlive(self->enemy) //someone dies || (self->owner->flags & FL_WORMHOLE) // owner enters a wormhole || (self->owner->client->tball_delay > level.time) //owner tballs away || (self->owner->flags & FL_CHATPROTECT) //3.0 owner is in chatprotect || ((self->owner->myskills.class_num == CLASS_POLTERGEIST) && (!self->owner->mtype) && !PM_PlayerHasMonster(self->owner)) //3.0 poltergeist is in human form || que_findtype(self->enemy->curses, NULL, HEALING) != NULL) //3.0 player is blessed with healing { que_removeent(self->enemy->curses, self, true); return; } VectorCopy(self->enemy->s.origin, self->s.origin); // follow enemy radius = PLAGUE_DEFAULT_RADIUS+PLAGUE_ADDON_RADIUS*self->owner->myskills.abilities[PLAGUE].current_level; if (radius > PLAGUE_MAX_RADIUS) radius = PLAGUE_MAX_RADIUS; // find someone nearby to infect while ((e = findradius(e, self->s.origin, radius)) != NULL) { if (e == self->enemy) continue; if (!G_ValidTarget(self, e, true)) continue; // don't allow more than one curse of the same type if (que_typeexists(e->curses, CURSE_PLAGUE)) continue; // holy water grants temporary immunity to curses if (e->holywaterProtection > level.time) continue; // spawn another plague cloud on this entity PlagueCloud(self->owner, e); } if (level.time > self->wait) { dmg = (float)self->owner->myskills.abilities[PLAGUE].current_level/10 * ((float)self->enemy->max_health/20); if (!self->enemy->client && strcmp(self->enemy->classname, "player_tank") != 0) dmg *= 2; // non-clients take double damage (helps with pvm) if (dmg < 1) dmg = 1; if (dmg > 100) dmg = 100; T_Damage(self->enemy, self->enemy, self->owner, vec3_origin, self->enemy->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ABILITIES, MOD_PLAGUE); // hurt 'em self->wait = level.time + PLAGUE_DELAY; } self->nextthink = level.time + FRAMETIME; }
void bombperson_think (edict_t *self) { int height, max_height; float bombtime, thinktime; vec3_t start; trace_t tr; // calculate drop rate bombtime = self->delay - 8; // max rate achieved 2 seconds after casting if (bombtime < level.time) bombtime = level.time; thinktime = level.time + 0.25 * ((bombtime + 1) - level.time); // max 1 bomb per 0.25 seconds // bomb self-terminates if the enemy dies or owner teleports away if (!G_EntIsAlive(self->owner) || !G_EntIsAlive(self->enemy) || (level.time > self->delay) || (self->owner->client->tball_delay > level.time)) { //RemoveCurse(self->enemy, self); que_removeent(self->enemy->curses, self, true); return; } VectorCopy(self->enemy->s.origin, self->s.origin); // gi.linkentity(self); /* // if the caster can't see his target, then pause the spell if (!visible(self->orb, self->owner)) { self->nextthink = thinktime; return; } */ // get random drop height max_height = 250 - (20 * self->owner->myskills.abilities[BOMB_SPELL].current_level); if (max_height < 150) max_height = 150; height = GetRandom(50, max_height) + self->enemy->maxs[2]; // drop bombs above target VectorCopy(self->s.origin, start); start[2] += height; tr = gi.trace(self->s.origin, self->mins, self->maxs, start, self->owner, MASK_SHOT); VectorCopy(tr.endpos, start); start[2]--; // spread randomly around target start[0] += (BOMBPERSON_WIDTH/2)*crandom(); start[1] += (BOMBPERSON_WIDTH/2)*crandom(); spawn_grenades(self->owner, start, (0.5+2*random()), self->dmg, 1); self->nextthink = thinktime; }
qboolean G_CurseValidTarget (edict_t *self, edict_t *target, qboolean vis, qboolean isCurse) { if (!G_EntIsAlive(target)) return false; // don't target players with invulnerability if (target->client && (target->client->invincible_framenum > level.framenum)) return false; // don't target spawning players if (target->client && (target->client->respawn_time > level.time)) return false; // don't target players in chat-protect if (!ptr->value && target->client && (target->flags & FL_CHATPROTECT)) return false; // don't target spawning world monsters if (target->activator && !target->activator->client && (target->svflags & SVF_MONSTER) && (target->deadflag != DEAD_DEAD) && (target->nextthink-level.time > 2*FRAMETIME)) return false; // don't target cloaked players if (target->client && target->svflags & SVF_NOCLIENT) return false; if (vis && !visible(self, target)) return false; if(que_typeexists(target->curses, CURSE_FROZEN)) return false; if (isCurse && (target->flags & FL_GODMODE || OnSameTeam(self, target))) return false; if (target == self) return false; return true; }
void CreateBoss (edict_t *ent) { edict_t *boss; //gi.dprintf("DEBUG: Waiting for player to become valid...\n"); boss = Boss_CreateTank(); // create the boss entity // ready the player and boss if the player is alive and there // is enough room to spawn the boss if (G_EntIsAlive(ent) && Boss_CanFit(ent, boss->mins, boss->maxs)) { Boss_ReadyPlayer(ent, boss); SelectedBossPlayer = NULL; // reset pointer so someone else can have a turn } else { if (level.time > boss_timeout) SelectedBossPlayer = NULL; // gi.dprintf("Waiting for the player to be valid...\n"); // remove the boss entity G_FreeEdict(boss); return; } }
void CurseEffects (edict_t *self, int num, int color) { vec3_t start, up, angle; if ((level.framenum % 5) != 0) return; if (!G_EntIsAlive(self)) return; VectorCopy(self->s.angles, angle); angle[ROLL] = GetRandom(0, 20) - 10; angle[PITCH] = GetRandom(0, 20) - 10; AngleCheck(&angle[ROLL]); AngleCheck(&angle[PITCH]); AngleVectors(angle, NULL, NULL, up); // upside-down minisentry if (self->owner && (self->mtype == M_MINISENTRY) && (self->owner->style == SENTRY_FLIPPED)) VectorMA(self->s.origin, self->mins[2]-16, up, start); else VectorMA(self->s.origin, self->maxs[2]+16, up, start); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_LASER_SPARKS); gi.WriteByte(num); // number of sparks gi.WritePosition(start); gi.WriteDir(up); gi.WriteByte(color); // 242 = red, 210 = green, 2 = black gi.multicast(start, MULTICAST_PVS); }
void forcewall_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { V_Touch(self, other, plane, surf); // let teammates pass thru if (/*(other != self->activator) &&*/ G_EntIsAlive(other)) { if (OnSameTeam(self->activator, other) && (other->movetype == MOVETYPE_STEP || other->movetype == MOVETYPE_WALK)) { //gi.dprintf("%s touching %d \n",other->classname, other->movetype); self->solid = SOLID_NOT; self->wait = level.time + 0.2; // small buffer so monsters can get thru } else if (level.time > self->random) { // dont allow solid force wall to trap people if (level.framenum == self->count-900) { G_FreeEdict(self); return; } forcewall_knockback(self, other); // knock them back gi.sound(other, CHAN_ITEM, gi.soundindex("world/force2.wav"), 1, ATTN_NORM, 0); self->random = level.time + 0.5; // dont spam the sound } } }
qboolean que_valident (que_t *que) { // 3.5 aura/curse is no longer valid if the owner dies return (que->ent && que->ent->inuse && (que->time > level.time) && G_EntIsAlive(que->ent->owner)); /* return (que->ent && que->ent->inuse && que->ent->owner && que->ent->owner->inuse && (que->time > level.time)); */ }
void CursedPlayer (edict_t *ent) { int i; vec3_t forward; if (!G_EntIsAlive(ent)) return; if (!ent->client) return; if (!que_typeexists(ent->curses, CURSE)) { // reset roll angles ent->client->ps.pmove.delta_angles[ROLL] = 0; return; } if (level.time > ent->curse_delay) { ent->curse_dir = GetRandom(1, 8); ent->curse_delay = level.time + 2*random(); } // copy current viewing angles VectorCopy(ent->client->v_angle, forward); // choose which direction to move angles switch (ent->curse_dir) { case 1: forward[PITCH]+=CURSE_MOVEMENT; break; // down case 2: forward[PITCH]-=CURSE_MOVEMENT; break; // up case 3: forward[YAW]+=CURSE_MOVEMENT; break; // left case 4: forward[YAW]-=CURSE_MOVEMENT; break; // right case 5: forward[YAW]+=CURSE_MOVEMENT; forward[PITCH]-=CURSE_MOVEMENT; break; case 6: forward[YAW]+=CURSE_MOVEMENT; forward[PITCH]+=CURSE_MOVEMENT; break; case 7: forward[YAW]-=CURSE_MOVEMENT; forward[PITCH]-=CURSE_MOVEMENT; break; case 8: forward[YAW]+=CURSE_MOVEMENT; forward[PITCH]+=CURSE_MOVEMENT; break; } // change roll angles if (ent->curse_dir <= 4) forward[ROLL] +=CURSE_MOVEMENT; else forward[ROLL] -=CURSE_MOVEMENT; // don't roll too much if ((forward[ROLL] > 0) && (forward[ROLL] > CURSE_MAX_ROLL)) forward[ROLL] = CURSE_MAX_ROLL; else if ((forward[ROLL] < 0) && (forward[ROLL] < -CURSE_MAX_ROLL)) forward[ROLL] = -CURSE_MAX_ROLL; // set view angles for (i = 0 ; i < 3 ; i++) ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(forward[i]-ent->client->resp.cmd_angles[i]); VectorCopy(forward, ent->client->ps.viewangles); VectorCopy(forward, ent->client->v_angle); }
void mybrain_jumpattack_landing (edict_t *self) { // expand the bbox again, we're standing up self->maxs[2] = 32; self->takedamage = DAMAGE_AIM; gi.linkentity (self); // attack right away if enemy is valid, visible, infront, and is moving if (G_EntIsAlive(self->enemy) && visible(self, self->enemy) && infront(self, self->enemy) && self->enemy->movetype) mybrain_attack3(self); }
void depot_think (edict_t *self) { if (!G_EntIsAlive(self->creator)) { depot_remove(self, NULL, false); return; } depot_effects(self); depot_add_inventory(self, qf2sf(100)); self->nextthink = level.time + FRAMETIME; }
// returns true if there is a nearby, visible player boss qboolean findNearbyBoss (edict_t *self) { edict_t *other=NULL; while ((other = findradius(other, self->s.origin, BOSS_ALLY_BONUS_RANGE)) != NULL) { if (!IsABoss(other)) continue; if (!G_EntIsAlive(other)) continue; return true; } return false; }
void mybrain_jump_hold (edict_t *self) { vec3_t v; if (G_EntIsAlive(self->monsterinfo.attacker)) { // face the attacker VectorSubtract(self->monsterinfo.attacker->s.origin, self->s.origin, v); self->ideal_yaw = vectoyaw(v); M_ChangeYaw(self); } // check for landing or jump timeout if (self->waterlevel || self->groundentity || (level.time > self->monsterinfo.pausetime)) self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; else self->monsterinfo.aiflags |= AI_HOLD_FRAME; }
void laser_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { //gi.dprintf("laser_touch\n"); //gi.dprintf("%s %s %s\n", OnSameTeam(ent, other)?"y":"n", ent->health<ent->max_health?"y":"n", level.framenum>ent->monsterinfo.regen_delay1?"y":"n"); if (G_EntIsAlive(other) && other->client && OnSameTeam(ent, other) // a player on our team && other->client->pers.inventory[power_cube_index] >= 5 // has power cubes && ent->creator->health < 0.5*ent->creator->max_health // only repair below 50% health (to prevent excessive cube use/repair noise) && level.framenum > ent->creator->monsterinfo.regen_delay1) // check delay { ent->creator->health = ent->creator->max_health; other->client->pers.inventory[power_cube_index] -= 5; ent->creator->monsterinfo.regen_delay1 = level.framenum + 20; gi.sound(other, CHAN_VOICE, gi.soundindex("weapons/repair.wav"), 1, ATTN_NORM, 0); gi.cprintf(other, PRINT_HIGH, "Emitter repaired. Maximum output: %d/%d damage.\n", ent->creator->health, ent->creator->max_health); } }
void mymedic_heal (edict_t *self) { // stop healing our target died, if they are fully healed, or // they have gone out of range while we are standing ground (can't reach them) if (!G_EntIsAlive(self->enemy) || !M_NeedRegen(self->enemy) || ((self->monsterinfo.aiflags & AI_STAND_GROUND) && (entdist(self, self->enemy) > 256))) { self->enemy = NULL; mymedic_stand(self); return; } // continue healing if our target is still in range and // there are no enemies around if (OnSameTeam(self, self->enemy) && (entdist(self, self->enemy) <= 256) && !mymedic_findenemy(self)) self->monsterinfo.currentmove = &mymedic_move_attackCable; else mymedic_run(self); }
void mymedic_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) { if (random() > 0.9) return; if (level.time < self->monsterinfo.dodge_time) return; if (OnSameTeam(self, attacker)) return; if (!self->enemy && G_EntIsAlive(attacker)) self->enemy = attacker; if (!radius) { self->monsterinfo.currentmove = &mymedic_move_duck; self->monsterinfo.dodge_time = level.time + 2.0; } else { mymedic_leap(self); self->monsterinfo.dodge_time = level.time + 3.0; } }
void bombarea_think (edict_t *self) { float thinktime, bombtime; vec3_t start; trace_t tr; if (!G_EntIsAlive(self->owner) || (level.time>self->delay)) { G_FreeEdict(self); return; } VectorCopy(self->s.origin, start); thinktime = 0.2 * ((self->delay-6)-level.time); if (thinktime < 0.2) thinktime = 0.2; // if the caster can't see his target, then pause the spell /* if (!visible(self, self->owner)) { self->nextthink = thinktime; return; } */ // spread randomly around target start[0] += GetRandom(0, BOMBAREA_WIDTH/2)*crandom(); start[1] += GetRandom(0, BOMBAREA_WIDTH/2)*crandom(); tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SHOT); if (self->s.angles[PITCH] == 90) bombtime = 1 + 2*random(); else bombtime = 0.5 + 2*random(); spawn_grenades(self->owner, tr.endpos, bombtime, self->dmg, 1); self->nextthink = level.time + thinktime; }
void mybrain_jumpattack_hold (edict_t *self) { int value=0; // we are on ground if (self->groundentity) value = 1; // we are in the water or passed jump timeout value else if (self->waterlevel || level.time > self->monsterinfo.pausetime) value = 2; // discontinue holding this frame if (value) { self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; if (value == 1) gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); } else { vec3_t v; // hold this frame self->monsterinfo.aiflags |= AI_HOLD_FRAME; // face the enemy if he's alive and visible if (G_EntIsAlive(self->enemy) && visible(self, self->enemy)) { VectorSubtract(self->enemy->s.origin, self->s.origin, v); self->ideal_yaw = vectoyaw(v); M_ChangeYaw(self); } } }
qboolean CanCurseTarget (edict_t *caster, edict_t *target, int type, qboolean isCurse, qboolean vis) { if (!G_EntIsAlive(target)) return false; // don't target players with invulnerability if (target->client && (target->client->invincible_framenum > level.framenum)) return false; // don't target spawning players if (target->client && (target->client->respawn_time > level.time)) return false; // don't target players in chat-protect if (!ptr->value && target->client && (target->flags & FL_CHATPROTECT)) return false; // don't target spawning world monsters if (target->activator && !target->activator->client && (target->svflags & SVF_MONSTER) && (target->deadflag != DEAD_DEAD) && (target->nextthink-level.time > 2*FRAMETIME)) return false; // don't target cloaked players if (target->client && target->svflags & SVF_NOCLIENT) return false; if (vis && !visible(caster, target)) return false; if(que_typeexists(target->curses, CURSE_FROZEN)) return false; if (isCurse && (target->flags & FL_GODMODE || OnSameTeam(caster, target))) return false; if (target == caster) return false; // holywater gives immunity to curses for a short time. if (target->holywaterProtection > level.time && type != BLESS && type != HEALING) return false; // don't allow bless on flag carrier if ((type == BLESS) && target->client && HasFlag(target)) return false; return true; }
void totem_general_think(edict_t *self) { edict_t *caster = self->activator; // Die if caster is not alive, or is not a valid ent if (!caster || !caster->client || !G_EntIsAlive(caster) || caster->flags & FL_CHATPROTECT) { RemoveTotem(self); return; } //Some players can have two totems out (with talent). Take cubes away from them every 5 seconds. if(level.framenum % 50 == 0 && caster->client && caster->totem2 == self) { int *cubes = &caster->client->pers.inventory[ITEM_INDEX(Fdi_POWERCUBE)]; //Talent: Totemic Focus. int cost = 10;//30 - getTalentLevel(caster, TALENT_TOTEM) * 5; if(*cubes < cost) { *cubes = 0; RemoveTotem(self); return; } *cubes -= cost; } if(self->delay < level.time) { switch(self->mtype) { case TOTEM_FIRE: FireTotem_think(self, caster); break; case TOTEM_WATER: WaterTotem_think(self, caster); break; case TOTEM_NATURE: NatureTotem_think(self, caster); break; default: break; } } // totem mastery allows regeneration if (level.time > self->lasthurt + 1.0 && !caster->myskills.abilities[TOTEM_MASTERY].disable && caster->myskills.abilities[TOTEM_MASTERY].current_level > 0) M_Regenerate(self, TOTEM_REGEN_FRAMES, TOTEM_REGEN_DELAY, 1.0, true, false, false, &self->monsterinfo.regen_delay1); //Rotate a little. self->s.angles[YAW] += 5; if(self->s.angles[YAW] == 360) self->s.angles[YAW] = 0; //GHz 4.32 // if position has been updated, check for ground entity if (self->linkcount != self->monsterinfo.linkcount) { self->monsterinfo.linkcount = self->linkcount; M_CheckGround (self); } // don't slide if (self->groundentity) VectorClear(self->velocity); M_CatagorizePosition (self); M_WorldEffects (self); //GHz totem_effects(self); self->nextthink = level.time + FRAMETIME; }
void forcewall_think(edict_t *self) { int dmg; vec3_t zvec={0,0,0}; trace_t tr; CTF_SummonableCheck(self); // wall will auto-remove if it is asked to if ((self->removetime > 0) && (level.time > self->removetime)) { if (self->activator && self->activator->inuse) safe_cprintf(self->activator, PRINT_HIGH, "Your wall was removed from enemy territory.\n"); self->think = BecomeTE; self->nextthink = level.time + FRAMETIME; return; } // wall must have an owner if (!G_EntIsAlive(self->activator) || (level.framenum > self->count)) { if (G_EntExists(self->activator)) safe_cprintf(self->activator, PRINT_HIGH, "Your wall faded away.\n"); BecomeTE(self); return; } forcewall_seteffects(self); // is this a solid wall? if (self->takedamage) { forcewall_regenerate(self); tr = gi.trace (self->s.origin, self->mins, self->maxs, self->s.origin, self, (MASK_PLAYERSOLID|MASK_MONSTERSOLID)); // reset solid state if nobody is in the wall if ((self->solid == SOLID_NOT) && !G_EntIsAlive(tr.ent) && (level.time > self->wait)) self->solid = SOLID_BBOX; } else { // find something to burn tr = gi.trace (self->s.origin, self->mins, self->maxs, self->s.origin, self, (MASK_PLAYERSOLID|MASK_MONSTERSOLID)); if (G_EntExists(tr.ent) && !OnSameTeam(self, tr.ent)) { if (tr.ent->client && (tr.ent->client->respawn_time == level.time)) { safe_cprintf(self->activator, PRINT_HIGH, "Your wall faded away because it was too close to a spawnpoint!\n"); BecomeTE(self); return; } dmg = 10 + 4*self->activator->myskills.abilities[FORCE_WALL].current_level; burn_person(tr.ent, self->activator, dmg); T_Damage (tr.ent, self, self->activator, zvec, tr.ent->s.origin, NULL, dmg, 1, DAMAGE_ENERGY, MOD_BURN); // wall can only deal so much damage before self-destructing self->health -= dmg; if (self->health < 0) { safe_cprintf(self->activator, PRINT_HIGH, "Your wall has expired.\n"); self->think = BecomeTE; self->nextthink = level.time + FRAMETIME; return; } } //gi.dprintf("found %s\n", tr.ent->classname); } if (self->delay-10 == level.time) safe_cprintf(self->activator, PRINT_HIGH, "Your wall will time-out in 10 seconds.\n"); self->nextthink = level.time + FRAMETIME; }
// modified SV_movestep for use with player-controlled monsters qboolean M_Move (edict_t *ent, vec3_t move, qboolean relink) { vec3_t oldorg, neworg, end; trace_t trace;//, tr; float stepsize=STEPSIZE; // try the move VectorCopy (ent->s.origin, oldorg); VectorAdd (ent->s.origin, move, neworg); neworg[2] += stepsize; VectorCopy (neworg, end); end[2] -= stepsize*2; trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); if (trace.allsolid) { // if we would have collided with a live entity, call its touch function // this prevents player-monsters from being invulnerable to obstacles if (G_EntIsAlive(trace.ent) && trace.ent->touch) trace.ent->touch(trace.ent, ent, &trace.plane, trace.surface); return false; } if (trace.startsolid) { neworg[2] -= stepsize; trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); if (trace.allsolid || trace.startsolid) return false; } if (trace.fraction == 1) { // gi.dprintf("going to fall\n"); // if monster had the ground pulled out, go ahead and fall // VectorSubtract(trace.endpos, oldorg, forward); // VectorMA(oldorg, 64, forward, end); if ( ent->flags & FL_PARTIALGROUND ) { VectorAdd (ent->s.origin, move, ent->s.origin); if (relink) { gi.linkentity (ent); G_TouchTriggers (ent); } ent->groundentity = NULL; return true; } } // check point traces down for dangling corners VectorCopy (trace.endpos, ent->s.origin); if (!M_CheckBottom (ent)) { if (ent->flags & FL_PARTIALGROUND) { // entity had floor mostly pulled out from underneath it // and is trying to correct if (relink) { gi.linkentity (ent); G_TouchTriggers (ent); } return true; } } if (ent->flags & FL_PARTIALGROUND) ent->flags &= ~FL_PARTIALGROUND; ent->groundentity = trace.ent; if (trace.ent) ent->groundentity_linkcount = trace.ent->linkcount; // the move is ok if (relink) { gi.linkentity (ent); G_TouchTriggers (ent); } return true; }
//FIXME since we need to test end position contents here, can we avoid doing //it again later in catagorize position? qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) { float dz; vec3_t oldorg, neworg, end; trace_t trace;//, tr; int i; float stepsize; vec3_t test; int contents; int jump=0; // try the move VectorCopy (ent->s.origin, oldorg); VectorAdd (ent->s.origin, move, neworg); // flying monsters don't step up if ((ent->flags & (FL_SWIM|FL_FLY)) || (ent->waterlevel > 1)) { // gi.dprintf("trying to swim\n"); // try one move with vertical motion, then one without for (i=0 ; i<2 ; i++) { VectorAdd (ent->s.origin, move, neworg); if (i == 0 && ent->enemy) { if (!ent->goalentity) ent->goalentity = ent->enemy; dz = ent->s.origin[2] - ent->goalentity->s.origin[2]; if (ent->goalentity->client) { if (dz > 40) neworg[2] -= 8; if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2))) if (dz < 30) neworg[2] += 8; } else { if (dz > 8) neworg[2] -= 8; else if (dz > 0) neworg[2] -= dz; else if (dz < -8) neworg[2] += 8; else neworg[2] += dz; } } trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); // fly monsters don't enter water voluntarily if (ent->flags & FL_FLY) { if (!ent->waterlevel) { test[0] = trace.endpos[0]; test[1] = trace.endpos[1]; test[2] = trace.endpos[2] + ent->mins[2] + 1; contents = gi.pointcontents(test); if (contents & MASK_WATER) return false; } } // swim monsters don't exit water voluntarily if (ent->flags & FL_SWIM) { if (ent->waterlevel < 2) { test[0] = trace.endpos[0]; test[1] = trace.endpos[1]; test[2] = trace.endpos[2] + ent->mins[2] + 1; contents = gi.pointcontents(test); if (!(contents & MASK_WATER)) return false; } } if (trace.fraction == 1) { VectorCopy (trace.endpos, ent->s.origin); if (relink) { gi.linkentity (ent); G_TouchTriggers (ent); } return true; } //gi.dprintf("swim move failed\n"); if (!ent->enemy) break; } return false; } // push down from a step height above the wished position if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) stepsize = STEPSIZE; else stepsize = 1; neworg[2] += stepsize; VectorCopy (neworg, end); end[2] -= stepsize*2; // this trace checks from a position one step above the entity (at top of bbox) // to one step below the entity (bottom of bbox) trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); // there is an obstruction bigger than a step if (trace.allsolid) //GHz START { // az: Regular monsters: don't jump on invasion mode... if (!G_GetClient(ent) && invasion->value) return false; // try to jump over it if (G_EntIsAlive(trace.ent) || !CanJumpUp(ent, neworg, end)) return false; else { jump = 1; } } //GHz END // not enough room at this height--head of bbox intersects something solid // so push down and just try to walk forward at floor height else if (trace.startsolid) { neworg[2] -= stepsize; trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); if (trace.allsolid || trace.startsolid) return false; } // don't go in to water if (ent->waterlevel == 0) { test[0] = trace.endpos[0]; test[1] = trace.endpos[1]; test[2] = trace.endpos[2] + ent->mins[2] + 1; contents = gi.pointcontents(test); if (contents & (CONTENTS_LAVA|CONTENTS_SLIME)) return false; } //GHz 5/8/2010 - don't get stuck on steep little ramps if (trace.fraction < 1 && trace.plane.normal[2] < 0.7) // too steep return false; //GHz START // if (CanJumpDown(ent, trace.endpos)) // jump = -1; //GHz END // VectorSubtract(trace.endpos, oldorg, forward); // VectorNormalize(forward); // VectorMA(oldorg, 32, forward, end); // VectorAdd(trace.endpos, move, end); // VectorAdd(end, move, end); if ((trace.fraction == 1) && (jump != 1)) { //gi.dprintf("going to fall\n"); // if monster had the ground pulled out, go ahead and fall // VectorSubtract(trace.endpos, oldorg, forward); // VectorMA(oldorg, 64, forward, end); if (!CanJumpDown(ent, trace.endpos)) { if ( ent->flags & FL_PARTIALGROUND ) { VectorAdd (ent->s.origin, move, ent->s.origin); if (relink) { gi.linkentity (ent); G_TouchTriggers (ent); } ent->groundentity = NULL; return true; } return false; // walked off an edge } else jump = -1; } // check point traces down for dangling corners //GHz START /* // fix for monsters walking thru walls tr = gi.trace(trace.endpos, ent->mins, ent->maxs, trace.endpos, ent, MASK_SOLID); if (tr.contents & MASK_SOLID) return false; */ //GHz END if (jump != 1) VectorCopy (trace.endpos, ent->s.origin); if (!M_CheckBottom (ent)) { //gi.dprintf("partial ground\n"); if (ent->flags & FL_PARTIALGROUND) { // entity had floor mostly pulled out from underneath it // and is trying to correct if (relink) { gi.linkentity (ent); G_TouchTriggers (ent); } return true; } if (CanJumpDown(ent, trace.endpos)) jump = -1; if (!jump) { VectorCopy (oldorg, ent->s.origin); return false; } } else if (jump == -1) jump = 0; /* if (jump) { VectorCopy(oldorg, ent->s.origin); CanJumpDown(ent, trace.endpos, true); VectorCopy(trace.endpos, ent->s.origin); } */ if ( ent->flags & FL_PARTIALGROUND ) { ent->flags &= ~FL_PARTIALGROUND; } ent->groundentity = trace.ent; if (trace.ent) ent->groundentity_linkcount = trace.ent->linkcount; if (jump == -1) { /* gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_DEBUGTRAIL); gi.WritePosition (oldorg); gi.WritePosition (end); gi.multicast (end, MULTICAST_ALL); */ //VectorScale(move, 10, ent->velocity); //ent->velocity[2] = 200; } else if (jump == 1) { ent->velocity[2] = 200; //gi.dprintf("jumped at %d\n",level.framenum); } // the move is ok if (relink) { gi.linkentity (ent); G_TouchTriggers (ent); } //gi.dprintf("moved successfully at %d\n", level.framenum); return true; }
/* ============= CheckAuraOwner Returns true if the owner is allowed to maintain an aura ============= */ qboolean CheckAuraOwner (edict_t *self, int aura_cost) { return (G_EntIsAlive(self->owner) && self->owner->client && (self->owner->client->pers.inventory[power_cube_index] >= aura_cost) && !self->owner->client->cloaking); }
void ParasiteAttack (edict_t *ent) { int damage, kick; vec3_t forward, right, start, end, offset; trace_t tr; if (debuginfo->value) gi.dprintf("%s just called ParasiteAttack()\n", ent->client->pers.userinfo); // terminate attack if (!G_EntIsAlive(ent) || (ent->parasite_frames > PARASITE_MAXFRAMES) || que_typeexists(ent->curses, CURSE_FROZEN)) { parasite_endattack(ent); return; } // calculate starting point AngleVectors (ent->client->v_angle, forward, right, NULL); VectorSet(offset, 0, 7, ent->viewheight-8); P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); // do we already have a valid target? if (G_ValidTarget(ent, ent->parasite_target, true) && (entdist(ent, ent->parasite_target) <= PARASITE_RANGE) && infov(ent, ent->parasite_target, 90)) { VectorSubtract(ent->parasite_target->s.origin, start, forward); VectorNormalize(forward); } VectorMA(start, PARASITE_RANGE, forward, end); tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT); // did we hit something? if (G_EntIsAlive(tr.ent) && !OnSameTeam(ent, tr.ent)) { if (ent->parasite_frames == 0) { ent->client->pers.inventory[power_cube_index] -= PARASITE_COST; gi.sound (ent, CHAN_AUTO, gi.soundindex("parasite/paratck3.wav"), 1, ATTN_NORM, 0); ent->client->ability_delay = level.time + PARASITE_DELAY; } ent->parasite_target = tr.ent; damage = 2*ent->myskills.abilities[BLOOD_SUCKER].current_level; if (tr.ent->groundentity) kick = -100; else kick = -50; T_Damage(tr.ent, ent, ent, forward, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_NO_ABILITIES, MOD_PARASITE); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_PARASITE_ATTACK); gi.WriteShort(ent-g_edicts); gi.WritePosition(start); gi.WritePosition(tr.endpos); gi.multicast(ent->s.origin, MULTICAST_PVS); ent->parasite_frames++; } else if (ent->parasite_frames) parasite_endattack(ent); }
void inv_defenderspawn_think (edict_t *self) { //int num=G_GetEntityIndex(self); // FOR DEBUGGING ONLY vec3_t start; trace_t tr; edict_t *monster=NULL; //FIXME: this isn't a good enough check if monster is dead or not // did our monster die? if (!G_EntIsAlive(self->enemy)) { if (self->orders == MONSTERSPAWN_STATUS_IDLE) { // wait some time before spawning another monster self->wait = level.time + 30; self->orders = MONSTERSPAWN_STATUS_WORKING; //gi.dprintf("%d: monster died, waiting to build monster...\n", num); } // try to spawn another if ((level.time > self->wait) && (monster = SpawnDrone(self, self->sounds, true)) != NULL) { //gi.dprintf("%d: attempting to spawn a monster\n", num); // get starting position VectorCopy(self->s.origin, start); start[2] = self->absmax[2] + 1 + abs(monster->mins[2]); tr = gi.trace(start, monster->mins, monster->maxs, start, NULL, MASK_SHOT); // kill dead bodies if (tr.ent && tr.ent->takedamage && (tr.ent->deadflag == DEAD_DEAD || tr.ent->health < 1)) T_Damage(tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, 10000, 0, 0, 0); // spawn is blocked, try again later else if (tr.fraction < 1) { //gi.dprintf("%d: spawn is blocked, will try again\n", num); G_FreeEdict(monster); self->nextthink = level.time + 1.0; return; } // should this monster stand ground? if (self->style) monster->monsterinfo.aiflags |= AI_STAND_GROUND; monster->s.angles[YAW] = self->s.angles[YAW]; // move the monster onto the spawn pad VectorCopy(start, monster->s.origin); VectorCopy(start, monster->s.old_origin); monster->s.event = EV_OTHER_TELEPORT; // give them quad/invuln to prevent spawn-camping if (self->count) monster->monsterinfo.inv_framenum = level.framenum + self->count; else monster->monsterinfo.inv_framenum = level.framenum + 60; gi.linkentity(monster); self->enemy = monster; // keep track of this monster //gi.dprintf("%d: spawned a monster successfully\n", num); } else { //if (level.time > self->wait) // gi.dprintf("%d: spawndrone() failed to spawn a monster\n", num); //else if (!(level.framenum%10)) // gi.dprintf("%d: waiting...\n", num); } } else { //if (self->orders == MONSTERSPAWN_STATUS_WORKING) // gi.dprintf("%d: spawn is now idle\n", num); self->orders = MONSTERSPAWN_STATUS_IDLE; } self->nextthink = level.time + FRAMETIME; }
void fire_think (edict_t *self) { edict_t *ed=NULL; qboolean quench = false; int i; int damage; // fire self-terminates if if (!G_EntIsAlive(self->enemy) || !G_EntIsAlive(self->owner)//owner dies || (level.time > self->delay)) // enemy dies //duration expires //|| que_findtype(self->enemy->curses, NULL, HEALING) != NULL) //3.0 when player is blessed with healing { que_removeent(self->enemy->curses, self, true); return; } //3.0 quench the flames if the player is in possesion of a flame stopping item if (!(self->enemy->waterlevel || self->waterlevel)) { for (i = 3; i < MAX_VRXITEMS; ++i) { if (self->enemy->myskills.items[i].itemtype & ITEM_FIRE_RESIST) { quench = true; break; } } } if (self->enemy->waterlevel || self->waterlevel || quench) { //if item and not water stopped the fire if (quench) { //Consume an item charge if (!(self->enemy->myskills.items[i].itemtype & ITEM_UNIQUE)) self->enemy->myskills.items[i].quantity -= 1; if(self->enemy->myskills.items[i].quantity == 0) { int count = 0; gi.cprintf(self->enemy, PRINT_HIGH, "Your burn resistant clothing has been destroyed!\n"); //erase the item V_ItemClear(&self->enemy->myskills.items[i]); //Tell the user if they have any left for (i = 3; i < MAX_VRXITEMS; ++i) if (self->enemy->myskills.items[i].itemtype & ITEM_FIRE_RESIST) count++; if (count) gi.cprintf(self->enemy, PRINT_HIGH, "You have %d left.\n", count); } } //water did it, so play a hissing dound else gi.sound (self->enemy, CHAN_WEAPON, gi.soundindex ("world/airhiss1.wav"), 1, ATTN_NORM, 0); que_removeent(self->enemy->curses, self, true); return; } damage = self->dmg; VectorCopy(self->enemy->s.origin,self->s.origin); if (self->PlasmaDelay < level.time) { T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_BURN); self->PlasmaDelay = level.time + 1; } self->nextthink = level.time + FRAMETIME; }
void boss_update (edict_t *ent, usercmd_t *ucmd) { int div=15, forwardspeed, sidespeed, maxspeed; int frames=0; vec3_t forward, right, angles; edict_t *boss; boss = ent->owner; // make sure this is a valid boss entity if (!G_EntIsAlive(boss)) return; if (!IsABoss(boss) && (boss->mtype != P_TANK)) return; VectorCopy(ent->s.origin, ent->s.old_origin); // update player state //ent->client->ps.pmove.pm_type = PM_FREEZE; ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; //ent->client->ability_delay = level.time + FRAMETIME; // copy player angles to boss boss->s.angles[YAW] = ent->s.angles[YAW]; boss->s.angles[PITCH] = 0; boss->s.angles[ROLL] = 0; AngleVectors(boss->s.angles, forward, right, NULL); vectoangles(right, angles); // move player into position boss_position_player(ent, boss); // speed divider, lower is faster (ucmd is 400) if (boss->mtype == BOSS_TANK) div = 20; else if (boss->mtype == BOSS_MAKRON) div = 10; else div = 26; // speed limiter, dont allow client to speed cheat using higher cl_speeds maxspeed = 400; forwardspeed = ucmd->forwardmove; sidespeed = ucmd->sidemove; //gi.dprintf("%d %d\n", forwardspeed, sidespeed); if ((forwardspeed > 0) && forwardspeed > maxspeed) forwardspeed = maxspeed; else if (forwardspeed < -maxspeed) forwardspeed = -maxspeed; if ((sidespeed > 0) && sidespeed > maxspeed) sidespeed = maxspeed; else if (sidespeed < -maxspeed) sidespeed = -maxspeed; //4.2 allow superspeed if (ent->superspeed && (level.time > ent->lasthurt + DAMAGE_ESCAPE_DELAY)) { forwardspeed *= 3; sidespeed *= 3; maxspeed = 3*BOSS_MAXVELOCITY; } else if (boss->monsterinfo.air_frames)// used for boost maxspeed = 9999; else maxspeed = BOSS_MAXVELOCITY; if (level.framenum >= boss->count) { if (!(ent->client->buttons & BUTTON_ATTACK)) { if (forwardspeed > 0) { M_walkmove(boss, boss->s.angles[YAW], forwardspeed/div); frames = FRAMES_RUN_FORWARD; } else if (forwardspeed < 0) { M_walkmove(boss, boss->s.angles[YAW], forwardspeed/div); frames = FRAMES_RUN_BACKWARD; } if (sidespeed > 0) { M_walkmove(boss, angles[YAW], sidespeed/div); frames = FRAMES_RUN_FORWARD; } else if (sidespeed < 0) { M_walkmove(boss, angles[YAW], sidespeed/div); frames = FRAMES_RUN_FORWARD; } } else { frames = FRAMES_ATTACK; } if (boss->groundentity) { boss->monsterinfo.air_frames = 0;//we're done boosting VectorClear(boss->velocity); if (ucmd->upmove > 0 && !ent->client->jump) { //gi.sound (ent, CHAN_WEAPON, gi.soundindex ("tank/sight1.wav"), 1, ATTN_NORM, 0); // jump in the direction we are trying to move boss->velocity[0] = 0.1*((forwardspeed*forward[0])+(sidespeed*right[0])); boss->velocity[1] = 0.1*((forwardspeed*forward[1])+(sidespeed*right[1])); boss->velocity[2] = 350; ent->client->jump = true; } boss->v_flags &= ~SFLG_DOUBLEJUMP; } else { if (ucmd->upmove < 1) ent->client->jump = false; if (CanDoubleJump(boss, ucmd)) { boss->velocity[2] += 350; boss->v_flags |= SFLG_DOUBLEJUMP; } // steer in the direction we are trying to move boss->velocity[0] += 0.1*((forwardspeed*forward[0])+(sidespeed*right[0])); boss->velocity[1] += 0.1*((forwardspeed*forward[1])+(sidespeed*right[1])); if (ucmd->upmove && (boss->waterlevel > 1)) { if (ucmd->upmove > 0) { if (boss->waterlevel == 2) boss->velocity[2] = 200; else if (boss->velocity[2] < 50) boss->velocity[2] = 50; } else { if (boss->velocity[2] > -50) boss->velocity[2] = -50; } } // don't move too fast if (boss->velocity[0] > maxspeed) boss->velocity[0] = maxspeed; if (boss->velocity[0] < -maxspeed) boss->velocity[0] = -maxspeed; if (boss->velocity[1] > maxspeed) boss->velocity[1] = maxspeed; if (boss->velocity[1] < -maxspeed) boss->velocity[1] = -maxspeed; } boss->style = frames; // set frame set boss should use gi.linkentity(boss); boss->count = level.framenum+1; } // move player into position //boss_position_player(ent, boss); // if we are standing on a live entity, call its touch function // this prevents player-bosses from being invulnerable to obstacles //FIXME: it may be dangerous to call a touch function without a NULL plane or surf /* if (boss->groundentity && G_EntIsAlive(boss->groundentity) && boss->groundentity->touch) boss->groundentity->touch(boss->groundentity, boss, NULL, NULL); */ }
void base_createturret (edict_t *self) { edict_t *sentry; vec3_t end; trace_t tr; int casterlevel, talentLevel; float ammo_mult=1.0; // make sure sentry has settled down if (!G_EntIsAlive(self->creator) || !minisentry_checkposition(self)) { if (self->creator) { self->creator->num_sentries--; if (self->creator->num_sentries < 0) self->creator->num_sentries = 0; } BecomeExplosion1(self); return; } self->movetype = MOVETYPE_NONE; // lock down base self->takedamage = DAMAGE_NO; // the base is invulnerable // 3.8 base bbox no longer necessary, turret takes over VectorClear(self->mins); VectorClear(self->maxs); self->solid = SOLID_NOT; // create basic ent for sentry sentry = G_Spawn(); sentry->creator = self->creator; sentry->owner = self; // the base becomes the owner VectorCopy(self->s.angles, sentry->s.angles); sentry->think = minisentry_think; sentry->nextthink = level.time + FRAMETIME; sentry->s.modelindex = gi.modelindex ("models/weapons/g_bfg/tris.md2"); sentry->s.renderfx |= RF_IR_VISIBLE; // who really wanted to chase sentries anyway // sentry->flags |= FL_CHASEABLE; // 3.65 indicates entity can be chase cammed sentry->solid = SOLID_BBOX; sentry->movetype = MOVETYPE_NONE; sentry->clipmask = MASK_MONSTERSOLID; sentry->mass = 100; sentry->classname = "msentrygun"; //sentry->viewheight = 16; sentry->takedamage = DAMAGE_AIM; sentry->mtype = M_MINISENTRY; sentry->touch = minisentry_touch; sentry->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; //Talent: Storage Upgrade talentLevel = getTalentLevel(self->creator, TALENT_STORAGE_UPGRADE); ammo_mult += 0.2 * talentLevel; // set ammo sentry->monsterinfo.jumpdn = SENTRY_MAX_AMMO * ammo_mult; // max ammo sentry->light_level = sentry->monsterinfo.jumpdn; // current ammo //get player's current_level for BUILD_SENTRY casterlevel = self->monsterinfo.level; sentry->monsterinfo.level = casterlevel; // used for adding monster exp sentry->monsterinfo.control_cost = 20; // used for adding monster exp //set health sentry->health = MINISENTRY_INITIAL_HEALTH + (MINISENTRY_ADDON_HEALTH * casterlevel); sentry->monsterinfo.power_armor_power = MINISENTRY_INITIAL_ARMOR + (MINISENTRY_ADDON_ARMOR * casterlevel); // if (sentry->health > MINISENTRY_MAX_HEALTH) // sentry->health = MINISENTRY_MAX_HEALTH; //if (sentry->monsterinfo.power_armor_power > MINISENTRY_MAX_ARMOR) // sentry->monsterinfo.power_armor_power = MINISENTRY_MAX_ARMOR; sentry->max_health = sentry->health; sentry->monsterinfo.max_armor = sentry->monsterinfo.power_armor_power; //set damage sentry->dmg = MINISENTRY_INITIAL_BULLET + (MINISENTRY_ADDON_BULLET * casterlevel);// bullet damage sentry->radius_dmg = MINISENTRY_INITIAL_ROCKET + (MINISENTRY_ADDON_ROCKET * casterlevel); // rocket damage if (sentry->dmg > MINISENTRY_MAX_BULLET) sentry->dmg = MINISENTRY_MAX_BULLET; if (sentry->radius_dmg > MINISENTRY_MAX_ROCKET) sentry->radius_dmg = MINISENTRY_MAX_ROCKET; sentry->die = minisentry_die; sentry->pain = minisentry_pain; sentry->yaw_speed = 5; if (self->style == SENTRY_UPRIGHT) { VectorSet(sentry->mins, -28, -28, -12); VectorSet(sentry->maxs, 28, 28, 24); VectorCopy(self->s.origin, end); //end[2] += self->maxs[2] + sentry->mins[2] + 1; end[2] += abs(sentry->mins[2])+1; } else { VectorSet(sentry->mins, -28, -28, -24); VectorSet(sentry->maxs, 28, 28, 12); VectorCopy(self->s.origin, end); //end[2] -= abs(self->mins[2]) + sentry->maxs[2] + 1; end[2] -= sentry->maxs[2]+1; } // make sure position is valid tr = gi.trace(end, sentry->mins, sentry->maxs, end, sentry, MASK_SHOT); if (tr.contents & MASK_SHOT) { if (self->creator) { self->creator->num_sentries--; if (self->creator->num_sentries < 0) self->creator->num_sentries = 0; } //gi.dprintf("%s\n", tr.ent?tr.ent->classname:"null"); BecomeExplosion1(self); BecomeExplosion1(sentry); return; } VectorCopy(tr.endpos, sentry->s.origin); VectorCopy(sentry->s.angles, sentry->move_angles);// save for idle animation gi.linkentity(sentry); gi.sound(sentry, CHAN_VOICE, gi.soundindex("weapons/turrset.wav"), 1, ATTN_NORM, 0); self->think = base_think; // base is done creating gun self->nextthink = level.time + FRAMETIME; }
void carpetbomb_think (edict_t *self) { float ceil; qboolean failed = false; vec3_t forward, right, start, end; trace_t tr, tr1; if (!G_EntIsAlive(self->owner) || (level.time>self->delay)) { G_FreeEdict(self); return; } // move forward AngleVectors(self->s.angles, forward, NULL, NULL); VectorMA(self->s.origin, GetRandom(CARPETBOMB_DAMAGE_RADIUS/2, CARPETBOMB_DAMAGE_RADIUS+1), forward, start); tr = gi.trace(self->s.origin, NULL, NULL, start, NULL, MASK_SOLID); VectorCopy(start, end); start[2]++; end[2] -= 8192; tr1 = gi.trace(start, NULL, NULL, end, NULL, MASK_SOLID); start[2]--; if ((tr.fraction < 1) || (start[2] != tr1.endpos[2])) { //gi.dprintf("will step down, start[2] %f tr1.endpos[2] %f\n", start[2], tr1.endpos[2]); // get current ceiling height VectorCopy(start, end); end[2] += 8192; tr = gi.trace(self->s.origin, NULL, NULL, end, NULL, MASK_SOLID); ceil = tr.endpos[2]; // push down from above desired position start[2] += CARPETBOMB_STEP_SIZE; if ((start[2] > ceil)) // dont go thru ceiling start[2] = ceil; VectorCopy(start, end); end[2] -= 8192; tr = gi.trace(start, NULL, NULL, end, NULL, MASK_SOLID); // dont go thru walls if (tr.allsolid) failed = true; // try a bit lower if (tr.startsolid) { start[2] -= CARPETBOMB_STEP_SIZE; tr = gi.trace(start, NULL, NULL, end, NULL, MASK_SOLID); if (tr.startsolid || tr.allsolid) failed = true; } // dont go into water if we aren't already submerged VectorCopy(tr.endpos, start); start[2] += 8; if (!self->waterlevel && (gi.pointcontents(start) & MASK_WATER)) failed = true; } // save position VectorCopy(tr.endpos, self->s.origin); VectorCopy(tr.endpos, start); // spawn explosions on either side // FIXME: step down from these positions, otherwise we get mid-air explosions! AngleVectors(self->s.angles, NULL, right, NULL); VectorMA(self->s.origin, (crandom()*GetRandom(CARPETBOMB_CARPET_WIDTH/4, CARPETBOMB_CARPET_WIDTH/2)), right, end); // make sure path is wide enough tr = gi.trace(self->s.origin, NULL, NULL, end, self, MASK_SHOT); VectorCopy(tr.endpos, self->s.origin); self->s.origin[2] += 32; //4.08 make sure the caster can see this spot //if (!visible(self->owner, self)) if (!G_IsClearPath(self, MASK_SOLID, self->move_origin, self->s.origin)) failed = true; // make sure bombspell is in a valid location if ((gi.pointcontents(self->s.origin) & CONTENTS_SOLID) || failed) { G_FreeEdict(self); return; } T_RadiusDamage(self, self->owner, self->dmg, NULL, self->dmg_radius, MOD_BOMBS); // write explosion effects gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_EXPLOSION1); gi.WritePosition (self->s.origin); gi.multicast (self->s.origin, MULTICAST_PVS); VectorCopy(start, self->s.origin); // retrieve starting position self->nextthink = level.time + FRAMETIME; gi.linkentity(self); }