/** * @brief Spawn a singleplayer 2x2 unit. */ static void G_Actor2x2Spawn (Edict* ent) { /* set properties */ level.num_2x2spawnpoints[ent->team]++; ent->classname = "ugv"; ent->type = ET_ACTOR2x2SPAWN; ent->fieldSize = ACTOR_SIZE_2x2; /* Spawning has already calculated the pos from the origin ( = center of the cell). Perfect for normal size actors. * For 2x2 actors, the origin(of the info_ box) is in the middle of the four cells. Using VecToPos on that origin * results in the upper right cell being the pos of the actor. But we want the lower left cell to be the pos of the * 2x2 actor because routing and pathfinding rely on that. So compensate for that. */ ent->pos[0]--; ent->pos[1]--; /* Fall to ground */ if (ent->pos[2] >= PATHFINDING_HEIGHT) ent->pos[2] = PATHFINDING_HEIGHT - 1; ent->pos[2] = gi.GridFall(ent->fieldSize, ent->pos); if (ent->pos[2] >= PATHFINDING_HEIGHT) gi.DPrintf("G_Actor2x2Spawn: Warning: z level is out of bounds: %i\n", ent->pos[2]); G_EdictCalcOrigin(ent); /* link it for collision detection */ ent->dir = AngleToDir(ent->angle); assert(ent->dir < CORE_DIRECTIONS); ent->solid = SOLID_BBOX; /* Set bounding box. Maybe this is already set in one of the spawn functions? */ if (ent->maxs[0] == 0) VectorSet(ent->maxs, PLAYER2x2_WIDTH, PLAYER2x2_WIDTH, PLAYER_STAND); if (ent->mins[0] == 0) VectorSet(ent->mins, -PLAYER2x2_WIDTH, -PLAYER2x2_WIDTH, PLAYER_MIN); }
/** * @brief Let an actor fall down if e.g. the func_breakable the actor was standing on was destroyed. * @param[in,out] ent The actor that should fall down * @todo Handle cases where the grid position the actor would fall to is occupied by another actor already. */ void G_ActorFall (edict_t *ent) { edict_t* entAtPos; const int oldZ = ent->pos[2]; ent->pos[2] = gi.GridFall(gi.routingMap, ent->fieldSize, ent->pos); if (oldZ == ent->pos[2]) return; entAtPos = G_GetEdictFromPos(ent->pos, ET_NULL); if (entAtPos != NULL && (G_IsBreakable(entAtPos) || G_IsBlockingMovementActor(entAtPos))) { const int diff = oldZ - ent->pos[2]; G_TakeDamage(entAtPos, (int)(FALLING_DAMAGE_FACTOR * (float)diff)); } G_EdictCalcOrigin(ent); gi.LinkEdict(ent); G_CheckVis(ent, true); G_EventActorFall(ent); gi.EndEvents(); }
/** * @brief Spawn a singleplayer 2x2 unit. */ static void G_Actor2x2Spawn (edict_t *ent) { /* set properties */ level.num_2x2spawnpoints[ent->team]++; ent->classname = "ugv"; ent->type = ET_ACTOR2x2SPAWN; ent->fieldSize = ACTOR_SIZE_2x2; /* Fall to ground */ if (ent->pos[2] >= PATHFINDING_HEIGHT) ent->pos[2] = PATHFINDING_HEIGHT - 1; ent->pos[2] = gi.GridFall(gi.routingMap, ent->fieldSize, ent->pos); if (ent->pos[2] >= PATHFINDING_HEIGHT) gi.DPrintf("G_Actor2x2Spawn: Warning: z level is out of bounds: %i\n", ent->pos[2]); G_EdictCalcOrigin(ent); /* link it for collision detection */ ent->dir = AngleToDir(ent->angle); assert(ent->dir < CORE_DIRECTIONS); ent->solid = SOLID_BBOX; /* Set bounding box. Maybe this is already set in one of the spawn functions? */ if (ent->maxs[0] == 0) VectorSet(ent->maxs, PLAYER2x2_WIDTH, PLAYER2x2_WIDTH, PLAYER_STAND); if (ent->mins[0] == 0) VectorSet(ent->mins, -PLAYER2x2_WIDTH, -PLAYER2x2_WIDTH, PLAYER_MIN); }
/** * @brief Spawns a new entity at the floor * @note This is e.g. used to place dropped weapons/items at the floor */ Edict* G_SpawnFloor (const pos3_t pos) { Edict* floorItem; floorItem = G_Spawn("item"); floorItem->type = ET_ITEM; /* make sure that the item is always on a field that even the smallest actor can reach */ floorItem->fieldSize = ACTOR_SIZE_NORMAL; VectorCopy(pos, floorItem->pos); floorItem->pos[2] = gi.GridFall(floorItem->fieldSize, floorItem->pos); G_EdictCalcOrigin(floorItem); return floorItem; }
/** * @brief Spawns a new entity at the floor * @note This is e.g. used to place dropped weapons/items at the floor */ edict_t *G_SpawnFloor (const pos3_t pos) { edict_t *floor; floor = G_Spawn(); floor->classname = "item"; floor->type = ET_ITEM; /* make sure that the item is always on a field that even the smallest actor can reach */ floor->fieldSize = ACTOR_SIZE_NORMAL; VectorCopy(pos, floor->pos); floor->pos[2] = gi.GridFall(gi.routingMap, floor->fieldSize, floor->pos); G_EdictCalcOrigin(floor); return floor; }
/** * @brief info_civilian_start (0 1 1) (-16 -16 -24) (16 16 32) * Way point for a civilian. * @sa SP_civilian_start * @todo These waypoints should be placeable by the human player (e.g. spawn a special particle on the waypoint) * to direct the civilians to a special location */ static void SP_civilian_target (edict_t *ent) { /* target point for which team */ ent->team = TEAM_CIVILIAN; ent->classname = "civtarget"; ent->type = ET_CIVILIANTARGET; ent->fieldSize = ACTOR_SIZE_NORMAL; /* to let the grid fall function work */ /* add the edict to the list of known waypoints */ G_AddToWayPointList(ent); /* fall to ground */ if (ent->pos[2] >= PATHFINDING_HEIGHT) ent->pos[2] = PATHFINDING_HEIGHT - 1; ent->pos[2] = gi.GridFall(gi.routingMap, ent->fieldSize, ent->pos); G_EdictCalcOrigin(ent); }
static void G_SpawnField (edict_t *ent, const char *classname, entity_type_t type, solid_t solid) { vec3_t particleOrigin; ent->classname = classname; ent->type = type; ent->fieldSize = ACTOR_SIZE_NORMAL; ent->solid = solid; VectorSet(ent->maxs, UNIT_SIZE / 2, UNIT_SIZE / 2, UNIT_HEIGHT / 2); VectorSet(ent->mins, -UNIT_SIZE / 2, -UNIT_SIZE / 2, -UNIT_HEIGHT / 2); G_EdictCalcOrigin(ent); ent->think = Think_SmokeAndFire; ent->nextthink = 1; ent->time = level.actualRound; gi.LinkEdict(ent); VectorCopy(ent->origin, particleOrigin); particleOrigin[2] -= GROUND_DELTA; ent->particleLink = G_SpawnParticle(particleOrigin, ent->spawnflags, ent->particle); }
static void G_SpawnSmoke (const vec3_t vec, const char *particle, int rounds) { pos3_t pos; edict_t *ent; VecToPos(vec, pos); ent = G_GetEdictFromPos(pos, ET_SMOKE); if (ent == NULL) { pos_t z = gi.GridFall(gi.routingMap, ACTOR_SIZE_NORMAL, pos); if (z != pos[2]) return; ent = G_Spawn(); VectorCopy(pos, ent->pos); G_EdictCalcOrigin(ent); ent->spawnflags = G_GetLevelFlagsFromPos(pos); ent->particle = particle; SP_misc_smoke(ent); } ent->count = rounds; }
/** * @brief Generates the client events that are send over the netchannel to move an actor * @param[in] player Player who is moving an actor * @param[in] visTeam The team to check the visibility for - if this is 0 we build the forbidden list * above all edicts - for the human controlled actors this would mean that clicking to a grid * position that is not reachable because an invisible actor is standing there would not result in * a single step - as the movement is aborted before. For AI movement this is in general @c 0 - but * not if they e.g. hide. * @param[in] ent Edict to move * @param[in] to The grid position to walk to * @sa CL_ActorStartMove * @sa PA_MOVE */ void G_ClientMove (const player_t * player, int visTeam, edict_t* ent, const pos3_t to) { int status, initTU; dvec_t dvtab[MAX_DVTAB]; int dir; byte numdv, length; pos3_t pos; float div; int oldState; int oldHP; bool autoCrouchRequired = false; byte crouchingState; if (VectorCompare(ent->pos, to)) return; /* check if action is possible */ if (!G_ActionCheckForCurrentTeam(player, ent, TU_MOVE_STRAIGHT)) return; crouchingState = G_IsCrouched(ent) ? 1 : 0; oldState = oldHP = 0; /* calculate move table */ G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); /* length of ROUTING_NOT_REACHABLE means not reachable */ if (length && length >= ROUTING_NOT_REACHABLE) return; /* Autostand: check if the actor is crouched and player wants autostanding...*/ if (crouchingState && player->autostand) { /* ...and if this is a long walk... */ if (SHOULD_USE_AUTOSTAND(length)) { /* ...make them stand first. If the player really wants them to walk a long * way crouched, he can move the actor in several stages. * Uses the threshold at which standing, moving and crouching again takes * fewer TU than just crawling while crouched. */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); /* change to stand state */ crouchingState = G_IsCrouched(ent) ? 1 : 0; if (!crouchingState) { G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); autoCrouchRequired = true; } } } /* this let the footstep sounds play even over network */ ent->think = G_PhysicsStep; ent->nextthink = level.time; /* assemble dvec-encoded move data */ VectorCopy(to, pos); initTU = ent->TU; numdv = G_FillDirectionTable(dvtab, lengthof(dvtab), crouchingState, pos); /* make sure to end any other pending events - we rely on EV_ACTOR_MOVE not being active anymore */ gi.EndEvents(); /* everything ok, found valid route? */ if (VectorCompare(pos, ent->pos)) { byte* stepAmount = NULL; int usedTUs = 0; /* no floor inventory at this point */ FLOOR(ent) = NULL; while (numdv > 0) { /* A flag to see if we needed to change crouch state */ int crouchFlag; const byte oldDir = ent->dir; int dvec; /* get next dvec */ numdv--; dvec = dvtab[numdv]; /* This is the direction to make the step into */ dir = getDVdir(dvec); /* turn around first */ status = G_ActorDoTurn(ent, dir); if (status & VIS_STOP) { autoCrouchRequired = false; if (ent->moveinfo.steps == 0) usedTUs += TU_TURN; break; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; /* if something appears on our route that didn't trigger a VIS_STOP, we have to * send the turn event if this is our first step */ if (oldDir != ent->dir && ent->moveinfo.steps == 0) { G_EventActorTurn(ent); usedTUs += TU_TURN; } break; } /* decrease TUs */ div = gi.GetTUsForDirection(dir, G_IsCrouched(ent)); if ((int) (usedTUs + div) > ent->TU) break; usedTUs += div; /* This is now a flag to indicate a change in crouching - we need this for * the stop in mid move call(s), because we need the updated entity position */ crouchFlag = 0; /* Calculate the new position after the decrease in TUs, otherwise the game * remembers the false position if the time runs out */ PosAddDV(ent->pos, crouchFlag, dvec); /* slower if crouched */ if (G_IsCrouched(ent)) ent->speed = ACTOR_SPEED_CROUCHED; else ent->speed = ACTOR_SPEED_NORMAL; ent->speed *= g_actorspeed->value; if (crouchFlag == 0) { /* No change in crouch */ edict_t* clientAction; int contentFlags; vec3_t pointTrace; G_EdictCalcOrigin(ent); VectorCopy(ent->origin, pointTrace); pointTrace[2] += PLAYER_MIN; contentFlags = gi.PointContents(pointTrace); /* link it at new position - this must be done for every edict * movement - to let the server know about it. */ gi.LinkEdict(ent); /* Only the PHALANX team has these stats right now. */ if (ent->chr.scoreMission) { float truediv = gi.GetTUsForDirection(dir, 0); /* regardless of crouching ! */ if (G_IsCrouched(ent)) ent->chr.scoreMission->movedCrouched += truediv; else ent->chr.scoreMission->movedNormal += truediv; } /* write the step to the net */ G_WriteStep(ent, &stepAmount, dvec, contentFlags); /* check if player appears/perishes, seen from other teams */ G_CheckVis(ent, true); /* check for anything appearing, seen by "the moving one" */ status = G_CheckVisTeamAll(ent->team, false, ent); /* Set ent->TU because the reaction code relies on ent->TU being accurate. */ G_ActorSetTU(ent, initTU - usedTUs); clientAction = ent->clientAction; oldState = ent->state; oldHP = ent->HP; /* check triggers at new position */ if (G_TouchTriggers(ent)) { if (!clientAction) status |= VIS_STOP; } G_TouchSolids(ent, 10.0f); /* state has changed - maybe we walked on a trigger_hurt */ if (oldState != ent->state) status |= VIS_STOP; else if (oldHP != ent->HP) status |= VIS_STOP; } else if (crouchFlag == 1) { /* Actor is standing */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } else if (crouchFlag == -1) { /* Actor is crouching and should stand up */ G_ClientStateChange(player, ent, STATE_CROUCHED, false); } /* check for reaction fire */ if (G_ReactionFireOnMovement(ent)) { status |= VIS_STOP; autoCrouchRequired = false; } /* check for death */ if (((oldHP != 0 && oldHP != ent->HP) || (oldState != ent->state)) && !G_IsDazed(ent)) { /** @todo Handle dazed via trigger_hurt */ /* maybe this was due to rf - then the G_ActorDie was already called */ if (!G_IsDead(ent)) { G_CheckDeathOrKnockout(ent, NULL, NULL, oldHP - ent->HP); } return; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv - 1)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; break; } /* Restore ent->TU because the movement code relies on it not being modified! */ G_ActorSetTU(ent, initTU); } /* submit the TUs / round down */ if (g_notu != NULL && g_notu->integer) G_ActorSetTU(ent, initTU); else G_ActorSetTU(ent, initTU - usedTUs); G_SendStats(ent); /* end the move */ G_GetFloorItems(ent); gi.EndEvents(); } if (autoCrouchRequired) { /* toggle back to crouched state */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } }
/** * @brief Creates a server's entity / program execution context * by parsing textual entity definitions out of an ent file. * @sa CM_EntityString * @sa SV_SpawnServer */ void G_SpawnEntities (const char *mapname, bool day, const char *entities) { int entnum; G_FreeTags(TAG_LEVEL); OBJZERO(level); level.pathingMap = (pathing_t *)G_TagMalloc(sizeof(*level.pathingMap), TAG_LEVEL); G_EdictsReset(); /* initialize reactionFire data */ G_ReactionFireTargetsInit(); Q_strncpyz(level.mapname, mapname, sizeof(level.mapname)); level.day = day; G_ResetClientData(); level.activeTeam = TEAM_NO_ACTIVE; level.actualRound = 1; level.hurtAliens = sv_hurtaliens->integer; ai_waypointList = NULL; /* parse ents */ entnum = 0; while (1) { edict_t *ent; /* parse the opening brace */ const char *token = Com_Parse(&entities); if (!entities) break; if (token[0] != '{') gi.Error("ED_LoadFromFile: found %s when expecting {", token); ent = G_Spawn(); entities = ED_ParseEdict(entities, ent); ent->mapNum = entnum++; /* Set the position of the entity */ VecToPos(ent->origin, ent->pos); /* Call this entity's specific initializer (sets ent->type) */ ED_CallSpawn(ent); /* if this entity is an bbox (e.g. actor), then center its origin based on its position */ if (ent->solid == SOLID_BBOX) G_EdictCalcOrigin(ent); } /* spawn ai players, if needed */ if (level.num_spawnpoints[TEAM_CIVILIAN]) { if (AI_CreatePlayer(TEAM_CIVILIAN) == NULL) gi.DPrintf("Could not create civilian\n"); } if ((sv_maxclients->integer == 1 || ai_numactors->integer) && level.num_spawnpoints[TEAM_ALIEN]) { if (AI_CreatePlayer(TEAM_ALIEN) == NULL) gi.DPrintf("Could not create alien\n"); } Com_Printf("Used inventory slots after ai spawn: %i\n", game.i.GetUsedSlots(&game.i)); G_FindEdictGroups(); }