/** * @brief Interpolates the camera movement from the given start point to the given end point * @sa CL_CameraMove * @sa CL_ViewCenterAtGridPosition * @param[in] from The grid position to start the camera movement from * @param[in] target The grid position to move the camera to */ void CL_CameraRoute (const pos3_t from, const pos3_t target) { if (!cl_centerview->integer) return; /* initialize the camera route variables */ PosToVec(from, routeFrom); PosToVec(target, routeDelta); VectorSubtract(routeDelta, routeFrom, routeDelta); routeDelta[2] = 0; routeDist = VectorLength(routeDelta); VectorNormalize(routeDelta); /* center the camera on the route starting position */ VectorCopy(routeFrom, cl.cam.origin); /* set the world level to the z axis value of the camera target * the camera lerp will do a smooth translate from the old level * to the new one */ Cvar_SetValue("cl_worldlevel", target[2]); VectorClear(cl.cam.speed); cameraRoute = true; CL_BlockBattlescapeEvents(true); }
/** * @param[in] mapTiles List of tiles the current (RMA-)map is composed of * @param[in] start trace start vector * @param[in] end trace end vector * @param[in] box The box we move through the world * @param[in] levelmask Selects which submodels get scanned. * @param[in] brushmask brushes the trace should stop at (see MASK_*) * @param[in] brushreject brushes the trace should ignore (see MASK_*) * @brief Traces all submodels in all tiles. Used by ufo and ufo_ded. */ trace_t CM_CompleteBoxTrace (mapTiles_t *mapTiles, const vec3_t start, const vec3_t end, const AABB &box, int levelmask, int brushmask, int brushreject) { trace_t newtr, tr; int tile, i; vec3_t smin, smax, emin, emax, wpmins, wpmaxs; const vec3_t offset = {UNIT_SIZE / 2, UNIT_SIZE / 2, UNIT_HEIGHT / 2}; OBJZERO(tr); tr.fraction = 2.0f; /* Prep the mins and maxs */ for (i = 0; i < 3; i++) { smin[i] = start[i] + std::min(box.mins[i], box.maxs[i]); smax[i] = start[i] + std::max(box.mins[i], box.maxs[i]); emin[i] = end[i] + std::min(box.mins[i], box.maxs[i]); emax[i] = end[i] + std::max(box.mins[i], box.maxs[i]); } /* trace against all loaded map tiles */ for (tile = 0; tile < mapTiles->numTiles; tile++) { mapTile_t *myTile = &mapTiles->mapTiles[tile]; PosToVec(myTile->wpMins, wpmins); VectorSubtract(wpmins, offset, wpmins); PosToVec(myTile->wpMaxs, wpmaxs); VectorAdd(wpmaxs, offset, wpmaxs); /* If the trace is completely outside of the tile, then skip it. */ if (smax[0] < wpmins[0] && emax[0] < wpmins[0]) continue; if (smax[1] < wpmins[1] && emax[1] < wpmins[1]) continue; if (smax[2] < wpmins[2] && emax[2] < wpmins[2]) continue; if (smin[0] > wpmaxs[0] && emin[0] > wpmaxs[0]) continue; if (smin[1] > wpmaxs[1] && emin[1] > wpmaxs[1]) continue; if (smin[2] > wpmaxs[2] && emin[2] > wpmaxs[2]) continue; newtr = TR_TileBoxTrace(myTile, start, end, box, levelmask, brushmask, brushreject); newtr.mapTile = tile; /* memorize the trace with the minimal fraction */ if (newtr.fraction == 0.0) return newtr; if (newtr.fraction < tr.fraction) tr = newtr; } return tr; }
/** * @param[in] mapTiles List of tiles the current (RMA-)map is composed of * @param[in] trLine The line to trace * @param[in] box The box we move through the world * @param[in] levelmask Selects which submodels get scanned. * @param[in] brushmask brushes the trace should stop at (see MASK_*) * @param[in] brushreject brushes the trace should ignore (see MASK_*) * @brief Traces all submodels in all tiles. Used by ufo and ufo_ded. */ trace_t CM_CompleteBoxTrace (mapTiles_t* mapTiles, const Line& trLine, const AABB& box, int levelmask, int brushmask, int brushreject) { int tile; vec3_t smin, smax, emin, emax, wpmins, wpmaxs; const vec3_t offset = {UNIT_SIZE / 2, UNIT_SIZE / 2, UNIT_HEIGHT / 2}; trace_t tr; tr.fraction = 2.0f; VectorCopy(trLine.stop, tr.endpos); /* optimistically set the endpos just in case we are outside of ALL the tiles, so TR_TileBoxTrace won't do it */ /* Prep the mins and maxs */ for (int i = 0; i < 3; i++) { smin[i] = trLine.start[i] + std::min(box.mins[i], box.maxs[i]); smax[i] = trLine.start[i] + std::max(box.mins[i], box.maxs[i]); emin[i] = trLine.stop[i] + std::min(box.mins[i], box.maxs[i]); emax[i] = trLine.stop[i] + std::max(box.mins[i], box.maxs[i]); } /* trace against all loaded map tiles */ for (tile = 0; tile < mapTiles->numTiles; tile++) { MapTile& myTile = mapTiles->mapTiles[tile]; PosToVec(myTile.wpMins, wpmins); VectorSubtract(wpmins, offset, wpmins); PosToVec(myTile.wpMaxs, wpmaxs); VectorAdd(wpmaxs, offset, wpmaxs); /* If the trace is completely outside of the tile, then skip it. */ if (smax[0] < wpmins[0] && emax[0] < wpmins[0]) continue; if (smax[1] < wpmins[1] && emax[1] < wpmins[1]) continue; if (smax[2] < wpmins[2] && emax[2] < wpmins[2]) continue; if (smin[0] > wpmaxs[0] && emin[0] > wpmaxs[0]) continue; if (smin[1] > wpmaxs[1] && emin[1] > wpmaxs[1]) continue; if (smin[2] > wpmaxs[2] && emin[2] > wpmaxs[2]) continue; trace_t newtr = TR_TileBoxTrace(&myTile, trLine.start, trLine.stop, box, levelmask, brushmask, brushreject); newtr.mapTile = tile; /* memorize the trace with the minimal fraction */ if (newtr.fraction == 0.0) return newtr; if (newtr.fraction < tr.fraction) tr = newtr; } return tr; }
/** * @brief Centers the camera on a given grid field * @sa CL_CameraMove * @sa LE_CenterView * @sa CL_CameraRoute */ void CL_ViewCenterAtGridPosition (const pos3_t pos) { vec3_t vec; PosToVec(pos, vec); VectorCopy(vec, cl.cam.origin); Cvar_SetValue("cl_worldlevel", pos[2]); }
static void LE_PlayFootStepSound (le_t* le) { if (Q_strvalid(le->teamDef->footstepSound)) { S_LoadAndPlaySample(le->teamDef->footstepSound, le->origin, SOUND_ATTN_NORM, SND_VOLUME_FOOTSTEPS); return; } /* walking in water will not play the normal footstep sounds */ if (!le->pathContents[le->pathPos]) { vec3_t from, to; /* prepare trace vectors */ PosToVec(le->pos, from); VectorCopy(from, to); /* we should really hit the ground with this */ to[2] -= UNIT_HEIGHT; const trace_t trace = CL_Trace(Line(from, to), AABB::EMPTY, nullptr, nullptr, MASK_SOLID, cl_worldlevel->integer); if (trace.surface) LE_PlaySoundFileAndParticleForSurface(le, trace.surface->name); } else LE_PlaySoundFileForContents(le, le->pathContents[le->pathPos]); }
/** * @brief Plays step sounds and draw particles for different terrain types * @param[in] le The local entity to play the sound and draw the particle for * @param[in] textureName The name of the texture the actor is standing on * @sa LET_PathMove */ static void LE_PlaySoundFileAndParticleForSurface (le_t* le, const char* textureName) { const terrainType_t* t = Com_GetTerrainType(textureName); if (!t) return; /* origin might not be up-to-date here - but pos should be */ vec3_t origin; PosToVec(le->pos, origin); /** @todo use the Grid_Fall method (ACTOR_SIZE_NORMAL) to ensure, that the particle is * drawn at the ground (if needed - maybe the origin is already ground aligned)*/ if (t->particle) { /* check whether actor is visible */ if (!LE_IsStunned(le) && LE_IsLivingAndVisibleActor(le)) CL_ParticleSpawn(t->particle, 0, origin); } if (t->footstepSound) { Com_DPrintf(DEBUG_SOUND, "LE_PlaySoundFileAndParticleForSurface: volume %.2f\n", t->footstepVolume); S_LoadAndPlaySample(t->footstepSound, origin, SOUND_ATTN_STATIC, t->footstepVolume); } }
/** * @param[in] l Routing lump ... (routing data lump from bsp file) * @param[in] sX The x position on the world plane (grid position) - values from -(PATHFINDING_WIDTH/2) up to PATHFINDING_WIDTH/2 are allowed * @param[in] sY The y position on the world plane (grid position) - values from -(PATHFINDING_WIDTH/2) up to PATHFINDING_WIDTH/2 are allowed * @param[in] sZ The height level on the world plane (grid position) - values from 0 - PATHFINDING_HEIGHT are allowed * @sa CM_AddMapTile * @todo TEST z-level routing */ static void CMod_LoadRouting (RoutingLump& routingLump, const std::string& name, const lump_t * l, byte* cModelBase, int sX, int sY, int sZ) { static routing_t tempMap[ACTOR_MAX_SIZE]; static routing_t clMap[ACTOR_MAX_SIZE]; static mapTile_t curTile; byte *source; int length; int x, y, z, size, dir; int minX, minY, minZ; int maxX, maxY, maxZ; unsigned int i; double start, end; const int targetLength = sizeof(curTile.wpMins) + sizeof(curTile.wpMaxs) + sizeof(tempMap); start = time(NULL); if (!GUINT32_TO_LE(l->filelen)) { g_error("CMod_LoadRouting: Map has NO routing lump"); return; } source = cModelBase + GUINT32_TO_LE(l->fileofs); i = CMod_DeCompressRouting(&source, (byte*) curTile.wpMins); length = i; i = CMod_DeCompressRouting(&source, (byte*) curTile.wpMaxs); length += i; i = CMod_DeCompressRouting(&source, (byte*) tempMap); length += i; if (length != targetLength) { g_error("CMod_LoadRouting: Map has BAD routing lump; expected %i got %i", targetLength, length); return; } /* endian swap possibly necessary */ for (i = 0; i < 3; i++) { curTile.wpMins[i] = GUINT32_TO_LE(curTile.wpMins[i]); curTile.wpMaxs[i] = GUINT32_TO_LE(curTile.wpMaxs[i]); } g_debug("wpMins:(%i, %i, %i) wpMaxs:(%i, %i, %i)\n", curTile.wpMins[0], curTile.wpMins[1], curTile.wpMins[2], curTile.wpMaxs[0], curTile.wpMaxs[1], curTile.wpMaxs[2]); /* Things that need to be done: * The floor, ceiling, and route data can be copied over from the map. * All data must be regenerated for cells with overlapping content or where new * model data is adjacent to a cell with existing model data. */ /* Copy the routing information into our master table */ minX = std::max(curTile.wpMins[0], 0); minY = std::max(curTile.wpMins[1], 0); minZ = std::max(curTile.wpMins[2], 0); maxX = std::min(curTile.wpMaxs[0], PATHFINDING_WIDTH - 1); maxY = std::min(curTile.wpMaxs[1], PATHFINDING_WIDTH - 1); maxZ = std::min(curTile.wpMaxs[2], PATHFINDING_HEIGHT - 1); /** @todo check whether we need this copy code */ for (size = 0; size < ACTOR_MAX_SIZE; size++) /* Adjust starting x and y by size to catch large actor cell overlap. */ for (y = minY - size; y <= maxY; y++) for (x = minX - size; x <= maxX; x++) { /* Just incase x or y start negative. */ if (x < 0 || y < 0) continue; for (z = minZ; z <= maxZ; z++) { clMap[size].floor[z][y][x] = tempMap[size].floor[z - sZ][y - sY][x - sX]; clMap[size].ceil[z][y][x] = tempMap[size].ceil[z - sZ][y - sY][x - sX]; for (dir = 0; dir < CORE_DIRECTIONS; dir++) { clMap[size].route[z][y][x][dir] = tempMap[size].route[z - sZ][y - sY][x - sX][dir]; clMap[size].stepup[z][y][x][dir] = tempMap[size].stepup[z - sZ][y - sY][x - sX][dir]; } } /* Update the reroute table */ /*if (!reroute[size][y][x]) { reroute[size][y][x] = numTiles + 1; } else { reroute[size][y][x] = ROUTING_NOT_REACHABLE; }*/ } g_message("Done copying data.\n"); for (size = 0; size < 1; size++) for (x = minX; x <= maxX; x++) for (y = minY; y <= maxY; y++) for (z = minZ; z <= maxZ; z++) { pos3_t pos = { x, y, z }; vec3_t vect; PosToVec(pos,vect); /**@todo add other data to constructor: accessibility + connection states */ RoutingLumpEntry entry = RoutingLumpEntry(Vector3(vect), (z + 1)); FillRoutingLumpEntry(entry, clMap, pos); /**@todo perhaps there is a better way than creating a const object for adding */ const RoutingLumpEntry toAdd = RoutingLumpEntry(entry); routingLump.add(toAdd); } end = time(NULL); g_message("Loaded routing for tile %s in %5.1fs\n", name.c_str(), end - start); }
/** * @brief This test cycles through the list of map definitions found in the maps.ufo script * and tries to find surfaces to stand on with no sound assigned to them. * * This test takes too long to be run every time testall is run. So it's set up almost like a game: * After 5 maps (the first of them is fully checked) with missing sounds, the test stops. * If we manage to 'clean' one of those 5 maps, the next map will show up in the next run. */ TEST_F(FootStepTest, DISABLED_MapDefsFootSteps) { const char* filterId = TEST_GetStringProperty("mapdef-id"); const mapDef_t* md; int mapCount = 0; // the number of maps read int badMapCount = 0; const int skipCount = 20; // to skip the first n mapDefs const int badMapCountMax = 25; // # of maps with missing sounds before this test stops const int mapCountMax = 150; // should of cause be higher than skip + bad const int texCountMax = 30; char texNames[texCountMax][MAX_QPATH]; bool done = false; OBJZERO(texNames); ASSERT_TRUE(csi.numMDs > 0); MapDef_Foreach(md) { if (md->mapTheme[0] == '.') continue; if (filterId && !Q_streq(filterId, md->id)) continue; mapCount++; if (mapCount <= skipCount) continue; /* use a known seed to reproduce an error */ unsigned int seed; if (TEST_ExistsProperty("mapdef-seed")) { seed = TEST_GetLongProperty("mapdef-seed"); } else { seed = (unsigned int) time(nullptr); } srand(seed); int count = 0; Com_Printf("testMapDefsFootSteps: Mapdef %s (seed %u)\n", md->id, seed); const char* asmName = (const char*)LIST_GetByIdx(md->params, 0); SV_Map(true, md->mapTheme, asmName); /* now that we have loaded the map, check all cells for walkable places */ GridBox mBox(sv->mapData.mapBox); // test ALL the cells #if !FOOTSTEPS_FULL if (mapCount >= skipCount + 4) { // after the first 4 maps, reduce the testing area const pos3_t center = {148, 128, 0}; mBox.set(center, center); // the box on the map we're testing mBox.expandXY(10); // just test a few cells around the center of the map mBox.maxs[2] = 2; // and 3 levels high } #endif mBox.clipToMaxBoundaries(); for (int x = mBox.getMinX(); x <= mBox.getMaxX() && !done; x++) { for (int y = mBox.getMinY(); y <= mBox.getMaxY() && !done; y++) { for (int z = mBox.getMinZ(); z <= mBox.getMaxZ(); z++) { const int floor = sv->mapData.routing.getFloor(1, x, y, z); if (floor < 0) // if we have a floor in that cell continue; const AABB noBox(vec3_origin, vec3_origin); // we're doing a point-trace const pos3_t cellPos = {(pos_t)x, (pos_t)y, (pos_t)z}; // the cell in question vec3_t from, to; PosToVec(cellPos, from); // the center of the cell VectorCopy(from, to); // also base for the endpoint of the trace from[2] -= UNIT_HEIGHT / 2; // bottom of the cell from[2] += (floor + 2) * QUANT; // add the height of the floor plus 2 QUANTS to[2] -= 2 * UNIT_HEIGHT; // we should really hit the ground with this const trace_t trace = SV_Trace(Line(from, to), noBox, nullptr, MASK_SOLID); if (!trace.surface) continue; const char* snd = SV_GetFootstepSound(trace.surface->name); if (snd) continue; for (int i = 0; i < texCountMax; ++i) { if (!texNames[i][0]) { // found a free slot ? Q_strncpyz(texNames[i], trace.surface->name, sizeof(texNames[i])); count++; break; } if (Q_streq(trace.surface->name, texNames[i])) // already there ? break; } if (count > texCountMax) { done = true; break; // the z-loop } } } } if (!texNames[0][0]) { Com_Printf("In map %s, asm %s: Nothing detected\n", md->mapTheme, asmName); } else { ++badMapCount; for (int i = 0; i < texCountMax; ++i) { if (texNames[i][0]) { Com_Printf("In map %s, asm %s: No sound for: %s\n", md->mapTheme, asmName, texNames[i]); } } } OBJZERO(texNames); SV_ShutdownGameProgs(); if (done || mapCount >= mapCountMax || badMapCount >= badMapCountMax) break; } }