enum struct LocationMeta { float pos[3]; float ang[3]; bool runto; bool jump; int attempts; // # of attempts player has moved until they will try to manage } // Game settings LocationMeta activeBotLocations[MAXPLAYERS+1]; methodmap MovePoints < ArrayList { public MovePoints() { return view_as(new ArrayList(sizeof(LocationMeta))); } public bool SaveMap(const char[] map, const char[] set = "default") { char buffer[256]; // guesswho folder should be created by ReloadMapDB BuildPath(Path_SM, buffer, sizeof(buffer), "data/guesswho/%s", map); CreateDirectory(buffer, FOLDER_PERMS); BuildPath(Path_SM, buffer, sizeof(buffer), "data/guesswho/%s/%s.txt", map, set); File file = OpenFile(buffer, "w+"); if(file != null) { file.WriteLine("px\tpy\tpz\tax\tay\taz"); LocationMeta meta; for(int i = 0; i < movePoints.Length; i++) { movePoints.GetArray(i, meta); file.WriteLine("%.1f %.1f %.1f %.1f %.1f %.1f", meta.pos[0], meta.pos[1], meta.pos[2], meta.ang[0], meta.ang[1], meta.ang[2]); } PrintToServer("[GuessWho] Saved %d locations to %s/%s.txt", movePoints.Length, map, set); file.Flush(); delete file; return true; } PrintToServer("[GuessWho] OpenFile(w+) returned null for %s", buffer); return false; } public static MovePoints LoadMap(const char[] map, const char[] set = "default") { char buffer[256]; BuildPath(Path_SM, buffer, sizeof(buffer), "data/guesswho/%s/%s.txt", map, set); LoadConfigForMap(map); File file = OpenFile(buffer, "r+"); if(file != null) { char line[64]; char pieces[16][6]; file.ReadLine(line, sizeof(line)); // Skip header float min = L4D2Direct_GetMapMaxFlowDistance(); float max = 0.0; MovePoints points = new MovePoints(); while(file.ReadLine(line, sizeof(line))) { ExplodeString(line, " ", pieces, 6, 16, false); LocationMeta meta; meta.pos[0] = StringToFloat(pieces[0]); meta.pos[1] = StringToFloat(pieces[1]); meta.pos[2] = StringToFloat(pieces[2]); meta.ang[0] = StringToFloat(pieces[3]); meta.ang[1] = StringToFloat(pieces[4]); meta.ang[2] = StringToFloat(pieces[5]); // Calculate the flow bounds Address nav = L4D2Direct_GetTerrorNavArea(meta.pos); if(nav == Address_Null) { nav = L4D_GetNearestNavArea(meta.pos); if(nav == Address_Null) { PrintToServer("[GuessWho] WARN: POINT AT (%f,%f,%f) IS INVALID (NO NAV AREA); skipping", meta.pos[0], meta.pos[1], meta.pos[2]); continue; } } float flow = L4D2Direct_GetTerrorNavAreaFlow(nav); if(flow < min) min = flow; else if(flow > max) max = flow; points.AddPoint(meta); } // Give some buffer space, to not trigger TOO FAR min -= FLOW_BOUND_BUFFER; max += FLOW_BOUND_BUFFER; movePoints.SetBounds(min, max); PrintToServer("[GuessWho] Loaded %d locations with bounds [%.0f, %.0f] for %s/%s", points.Length, min, max, map, set); delete file; return points; } PrintToServer("[GuessWho] No point data for %s/%s", map, set); return null; } property float MinFlow { public get() { return flowMin; } } property float MaxFlow { public get() { return flowMax; } } public void SetBounds(float min, float max) { flowMin = min; flowMax = max; } public void GetRandomPoint(LocationMeta meta) { if(this.Length == 0) return; meta.runto = GetURandomFloat() < BOT_MOVE_RUN_CHANCE; meta.attempts = 0; this.GetArray(GetURandomInt() % this.Length, meta); #if defined DEBUG_SHOW_POINTS Effect_DrawBeamBoxRotatableToAll(meta.pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 255, 120}, 0); #endif } public bool GetRandomPointFar(const float src[3], float pos[3], float distanceAway = 100.0, int tries = 3) { while(tries-- > 0) { this.GetArray(GetURandomInt() % this.Length, pos); if(FloatAbs(GetVectorDistance(src, pos)) > distanceAway) { return true; } } return false; } public bool AddPoint(LocationMeta meta) { bool hitLimit = false; if(this.Length + 1 > MAX_VALID_LOCATIONS) { PrintToServer("[GuessWho] Hit MAX_VALID_LOCATIONS (%d), clearing some locations", MAX_VALID_LOCATIONS); this.Sort(Sort_Random, Sort_Float); this.Erase(RoundFloat(MAX_VALID_LOCATIONS * MAX_VALID_LOCATIONS_KEEP_PERCENT)); hitLimit = true; } this.PushArray(meta); return hitLimit; } }