Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "MessageHandler.h"
21 : #include "../CommandProc.h"
22 : #include "../GameLoop.h"
23 : #include "../MessagePasser.h"
24 :
25 : #include "graphics/GameView.h"
26 : #include "graphics/LOSTexture.h"
27 : #include "graphics/MapIO.h"
28 : #include "graphics/MapWriter.h"
29 : #include "graphics/MiniMapTexture.h"
30 : #include "graphics/Patch.h"
31 : #include "graphics/Terrain.h"
32 : #include "graphics/TerrainTextureEntry.h"
33 : #include "graphics/TerrainTextureManager.h"
34 : #include "lib/bits.h"
35 : #include "lib/file/vfs/vfs_path.h"
36 : #include "lib/status.h"
37 : #include "maths/MathUtil.h"
38 : #include "ps/CLogger.h"
39 : #include "ps/Filesystem.h"
40 : #include "ps/Game.h"
41 : #include "ps/GameSetup/GameSetup.h"
42 : #include "ps/Loader.h"
43 : #include "ps/World.h"
44 : #include "renderer/Renderer.h"
45 : #include "renderer/SceneRenderer.h"
46 : #include "renderer/WaterManager.h"
47 : #include "scriptinterface/Object.h"
48 : #include "scriptinterface/JSON.h"
49 : #include "simulation2/Simulation2.h"
50 : #include "simulation2/components/ICmpOwnership.h"
51 : #include "simulation2/components/ICmpPlayer.h"
52 : #include "simulation2/components/ICmpPlayerManager.h"
53 : #include "simulation2/components/ICmpPosition.h"
54 : #include "simulation2/components/ICmpRangeManager.h"
55 : #include "simulation2/components/ICmpTemplateManager.h"
56 : #include "simulation2/components/ICmpTerrain.h"
57 : #include "simulation2/components/ICmpVisual.h"
58 : #include "simulation2/system/ParamNode.h"
59 :
60 :
61 : #ifdef _MSC_VER
62 : # pragma warning(disable: 4458) // Declaration hides class member.
63 : #endif
64 :
65 : namespace
66 : {
67 0 : void InitGame()
68 : {
69 0 : if (g_Game)
70 : {
71 0 : delete g_Game;
72 0 : g_Game = NULL;
73 : }
74 :
75 0 : g_Game = new CGame(false);
76 :
77 : // Default to player 1 for playtesting
78 0 : g_Game->SetPlayerID(1);
79 0 : }
80 :
81 0 : void StartGame(JS::MutableHandleValue attrs)
82 : {
83 0 : g_Game->StartGame(attrs, "");
84 :
85 : // TODO: Non progressive load can fail - need a decent way to handle this
86 0 : LDR_NonprogressiveLoad();
87 :
88 : // Disable fog-of-war - this must be done before starting the game,
89 : // as visual actors cache their visibility state on first render.
90 0 : CmpPtr<ICmpRangeManager> cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
91 0 : if (cmpRangeManager)
92 0 : cmpRangeManager->SetLosRevealAll(-1, true);
93 :
94 0 : PSRETURN ret = g_Game->ReallyStartGame();
95 0 : ENSURE(ret == PSRETURN_OK);
96 0 : }
97 : }
98 :
99 : namespace AtlasMessage {
100 :
101 0 : QUERYHANDLER(GenerateMap)
102 : {
103 : try
104 : {
105 0 : InitGame();
106 :
107 : // Random map
108 0 : const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
109 0 : ScriptRequest rq(scriptInterface);
110 :
111 0 : JS::RootedValue settings(rq.cx);
112 0 : Script::ParseJSON(rq, *msg->settings, &settings);
113 0 : Script::SetProperty(rq, settings, "mapType", "random");
114 :
115 0 : JS::RootedValue attrs(rq.cx);
116 0 : Script::CreateObject(
117 : rq,
118 : &attrs,
119 : "mapType", "random",
120 0 : "script", *msg->filename,
121 : "settings", settings);
122 :
123 0 : StartGame(&attrs);
124 :
125 0 : msg->status = 0;
126 : }
127 0 : catch (PSERROR_Game_World_MapLoadFailed&)
128 : {
129 : // Cancel loading
130 0 : LDR_Cancel();
131 :
132 : // Since map generation failed and we don't know why, use the blank map as a fallback
133 :
134 0 : InitGame();
135 :
136 0 : const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
137 0 : ScriptRequest rq(scriptInterface);
138 :
139 : // Set up 8-element array of empty objects to satisfy init
140 0 : JS::RootedValue playerData(rq.cx);
141 0 : Script::CreateArray(rq, &playerData);
142 :
143 0 : for (int i = 0; i < 8; ++i)
144 : {
145 0 : JS::RootedValue player(rq.cx);
146 0 : Script::CreateObject(rq, &player);
147 0 : Script::SetPropertyInt(rq, playerData, i, player);
148 : }
149 :
150 0 : JS::RootedValue settings(rq.cx);
151 0 : Script::CreateObject(
152 : rq,
153 : &settings,
154 : "mapType", "scenario",
155 : "PlayerData", playerData);
156 :
157 0 : JS::RootedValue attrs(rq.cx);
158 0 : Script::CreateObject(
159 : rq,
160 : &attrs,
161 : "mapType", "scenario",
162 : "map", "maps/scenarios/_default",
163 : "settings", settings);
164 :
165 0 : StartGame(&attrs);
166 :
167 0 : msg->status = -1;
168 : }
169 0 : }
170 :
171 0 : MESSAGEHANDLER(LoadMap)
172 : {
173 0 : InitGame();
174 :
175 0 : const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
176 0 : ScriptRequest rq(scriptInterface);
177 :
178 : // Scenario
179 0 : CStrW map = *msg->filename;
180 0 : CStrW mapBase = map.BeforeLast(L".pmp"); // strip the file extension, if any
181 :
182 0 : JS::RootedValue attrs(rq.cx);
183 :
184 0 : Script::CreateObject(
185 : rq,
186 : &attrs,
187 : "mapType", "scenario",
188 : "map", mapBase);
189 :
190 0 : StartGame(&attrs);
191 0 : }
192 :
193 0 : MESSAGEHANDLER(ImportHeightmap)
194 : {
195 0 : std::vector<u16> heightmap_source;
196 0 : if (LoadHeightmapImageOs(*msg->filename, heightmap_source) != INFO::OK)
197 : {
198 0 : LOGERROR("Failed to decode heightmap.");
199 0 : return;
200 : }
201 :
202 : // resize terrain to heightmap size
203 : // Notice that the number of tiles/pixels per side of the heightmap image is
204 : // one less than the number of vertices per side of the heightmap.
205 0 : CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
206 0 : const ssize_t newSize = (sqrt(heightmap_source.size()) - 1) / PATCH_SIZE;
207 0 : const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2;
208 0 : terrain->ResizeAndOffset(newSize, offset, offset);
209 :
210 : // copy heightmap data into map
211 0 : u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap();
212 0 : ENSURE(heightmap_source.size() == (std::size_t) SQR(g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide()));
213 0 : std::copy(heightmap_source.begin(), heightmap_source.end(), heightmap);
214 :
215 : // update simulation
216 0 : CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
217 0 : if (cmpTerrain)
218 0 : cmpTerrain->ReloadTerrain();
219 :
220 0 : g_Game->GetView()->GetLOSTexture().MakeDirty();
221 : }
222 :
223 0 : MESSAGEHANDLER(SaveMap)
224 : {
225 0 : CMapWriter writer;
226 0 : VfsPath pathname = VfsPath(*msg->filename).ChangeExtension(L".pmp");
227 0 : writer.SaveMap(pathname,
228 : g_Game->GetWorld()->GetTerrain(),
229 0 : &g_Renderer.GetSceneRenderer().GetWaterManager(), &g_Renderer.GetSceneRenderer().GetSkyManager(),
230 : &g_LightEnv, g_Game->GetView()->GetCamera(), g_Game->GetView()->GetCinema(),
231 0 : &g_Renderer.GetPostprocManager(),
232 : g_Game->GetSimulation2());
233 0 : }
234 :
235 0 : QUERYHANDLER(GetMapSettings)
236 : {
237 0 : msg->settings = g_Game->GetSimulation2()->GetMapSettingsString();
238 0 : }
239 :
240 0 : BEGIN_COMMAND(SetMapSettings)
241 : {
242 : std::string m_OldSettings, m_NewSettings;
243 :
244 0 : void SetSettings(const std::string& settings)
245 : {
246 0 : g_Game->GetSimulation2()->SetMapSettings(settings);
247 0 : }
248 :
249 0 : void Do()
250 : {
251 0 : m_OldSettings = g_Game->GetSimulation2()->GetMapSettingsString();
252 0 : m_NewSettings = *msg->settings;
253 :
254 0 : SetSettings(m_NewSettings);
255 0 : }
256 :
257 : // TODO: we need some way to notify the Atlas UI when the settings are changed
258 : // externally, otherwise this will have no visible effect
259 0 : void Undo()
260 : {
261 : // SetSettings(m_OldSettings);
262 0 : }
263 :
264 0 : void Redo()
265 : {
266 : // SetSettings(m_NewSettings);
267 0 : }
268 :
269 0 : void MergeIntoPrevious(cSetMapSettings* prev)
270 : {
271 0 : prev->m_NewSettings = m_NewSettings;
272 0 : }
273 : };
274 0 : END_COMMAND(SetMapSettings)
275 :
276 0 : MESSAGEHANDLER(LoadPlayerSettings)
277 : {
278 0 : g_Game->GetSimulation2()->LoadPlayerSettings(msg->newplayers);
279 0 : }
280 :
281 0 : QUERYHANDLER(GetMapSizes)
282 : {
283 0 : msg->sizes = g_Game->GetSimulation2()->GetMapSizes();
284 0 : }
285 :
286 0 : QUERYHANDLER(RasterizeMinimap)
287 : {
288 : // TODO: remove the code duplication of the rasterization algorithm, using
289 : // CMinimap version.
290 0 : const CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
291 0 : const ssize_t dimension = terrain->GetVerticesPerSide() - 1;
292 0 : const ssize_t bpp = 24;
293 0 : const ssize_t imageDataSize = dimension * dimension * (bpp / 8);
294 :
295 0 : std::vector<u8> imageBytes(imageDataSize);
296 :
297 0 : float shallowPassageHeight = CMiniMapTexture::GetShallowPassageHeight();
298 :
299 0 : ssize_t w = dimension;
300 0 : ssize_t h = dimension;
301 0 : const float waterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight;
302 :
303 0 : for (ssize_t j = 0; j < h; ++j)
304 : {
305 : // Work backwards to vertically flip the image.
306 0 : ssize_t position = 3 * (h - j - 1) * dimension;
307 0 : for (ssize_t i = 0; i < w; ++i)
308 : {
309 0 : float avgHeight = (terrain->GetVertexGroundLevel(i, j)
310 0 : + terrain->GetVertexGroundLevel(i + 1, j)
311 0 : + terrain->GetVertexGroundLevel(i, j + 1)
312 0 : + terrain->GetVertexGroundLevel(i + 1, j + 1)
313 0 : ) / 4.0f;
314 :
315 0 : if (avgHeight < waterHeight && avgHeight > waterHeight - shallowPassageHeight)
316 : {
317 : // shallow water
318 0 : imageBytes[position++] = 0x70;
319 0 : imageBytes[position++] = 0x98;
320 0 : imageBytes[position++] = 0xc0;
321 : }
322 0 : else if (avgHeight < waterHeight)
323 : {
324 : // Set water as constant color for consistency on different maps
325 0 : imageBytes[position++] = 0x50;
326 0 : imageBytes[position++] = 0x78;
327 0 : imageBytes[position++] = 0xa0;
328 : }
329 : else
330 : {
331 0 : u32 color = std::numeric_limits<u32>::max();
332 0 : u32 hmap = static_cast<u32>(terrain->GetHeightMap()[j * dimension + i]) >> 8;
333 0 : float scale = hmap / 3.0f + 170.0f / 255.0f;
334 :
335 0 : CMiniPatch* mp = terrain->GetTile(i, j);
336 0 : if (mp)
337 : {
338 0 : CTerrainTextureEntry* tex = mp->GetTextureEntry();
339 0 : if (tex)
340 0 : color = tex->GetBaseColor();
341 : }
342 :
343 : // Convert
344 0 : imageBytes[position++] = static_cast<u8>(static_cast<float>(color & 0xff) * scale);
345 0 : imageBytes[position++] = static_cast<u8>(static_cast<float>((color >> 8) & 0xff) * scale);
346 0 : imageBytes[position++] = static_cast<u8>(static_cast<float>((color >> 16) & 0xff) * scale);
347 : }
348 : }
349 : }
350 :
351 0 : msg->imageBytes = std::move(imageBytes);
352 0 : msg->dimension = dimension;
353 0 : }
354 :
355 0 : QUERYHANDLER(GetRMSData)
356 : {
357 0 : msg->data = g_Game->GetSimulation2()->GetRMSData();
358 0 : }
359 :
360 0 : QUERYHANDLER(GetCurrentMapSize)
361 : {
362 0 : msg->size = g_Game->GetWorld()->GetTerrain()->GetTilesPerSide();
363 0 : }
364 :
365 0 : BEGIN_COMMAND(ResizeMap)
366 : {
367 0 : bool Within(const CFixedVector3D& pos, const int centerX, const int centerZ, const int radius)
368 : {
369 0 : int dx = abs(pos.X.ToInt_RoundToZero() - centerX);
370 0 : if (dx > radius)
371 0 : return false;
372 0 : int dz = abs(pos.Z.ToInt_RoundToZero() - centerZ);
373 0 : if (dz > radius)
374 0 : return false;
375 0 : if (dx + dz <= radius)
376 0 : return true;
377 0 : return dx * dx + dz * dz <= radius * radius;
378 : }
379 :
380 0 : struct DeletedObject
381 : {
382 : entity_id_t entityId;
383 : CStr templateName;
384 : player_id_t owner;
385 : CFixedVector3D pos;
386 : CFixedVector3D rot;
387 : u32 actorSeed;
388 : };
389 :
390 : ssize_t m_OldPatches, m_NewPatches;
391 : int m_OffsetX, m_OffsetY;
392 :
393 : u16* m_Heightmap;
394 : CPatch* m_Patches;
395 :
396 : std::vector<DeletedObject> m_DeletedObjects;
397 : std::vector<std::pair<entity_id_t, CFixedVector3D>> m_OldPositions;
398 : std::vector<std::pair<entity_id_t, CFixedVector3D>> m_NewPositions;
399 :
400 0 : cResizeMap() : m_Heightmap(nullptr), m_Patches(nullptr)
401 : {
402 0 : }
403 :
404 0 : ~cResizeMap()
405 0 : {
406 0 : delete[] m_Heightmap;
407 0 : delete[] m_Patches;
408 0 : }
409 :
410 0 : void MakeDirty()
411 : {
412 0 : CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
413 0 : if (cmpTerrain)
414 0 : cmpTerrain->ReloadTerrain();
415 :
416 : // The LOS texture won't normally get updated when running Atlas
417 : // (since there's no simulation updates), so explicitly dirty it
418 0 : g_Game->GetView()->GetLOSTexture().MakeDirty();
419 0 : }
420 :
421 0 : void ResizeTerrain(ssize_t patches, int offsetX, int offsetY)
422 : {
423 0 : CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
424 0 : terrain->ResizeAndOffset(patches, -offsetX, -offsetY);
425 0 : }
426 :
427 0 : void DeleteObjects(const std::vector<DeletedObject>& deletedObjects)
428 : {
429 0 : for (const DeletedObject& deleted : deletedObjects)
430 0 : g_Game->GetSimulation2()->DestroyEntity(deleted.entityId);
431 :
432 0 : g_Game->GetSimulation2()->FlushDestroyedEntities();
433 0 : }
434 :
435 0 : void RestoreObjects(const std::vector<DeletedObject>& deletedObjects)
436 : {
437 0 : CSimulation2& sim = *g_Game->GetSimulation2();
438 :
439 0 : for (const DeletedObject& deleted : deletedObjects)
440 : {
441 0 : entity_id_t ent = sim.AddEntity(deleted.templateName.FromUTF8(), deleted.entityId);
442 0 : if (ent == INVALID_ENTITY)
443 : {
444 0 : LOGERROR("Failed to load entity template '%s'", deleted.templateName.c_str());
445 : }
446 : else
447 : {
448 0 : CmpPtr<ICmpPosition> cmpPosition(sim, deleted.entityId);
449 0 : if (cmpPosition)
450 : {
451 0 : cmpPosition->JumpTo(deleted.pos.X, deleted.pos.Z);
452 0 : cmpPosition->SetXZRotation(deleted.rot.X, deleted.rot.Z);
453 0 : cmpPosition->SetYRotation(deleted.rot.Y);
454 : }
455 :
456 0 : CmpPtr<ICmpOwnership> cmpOwnership(sim, deleted.entityId);
457 0 : if (cmpOwnership)
458 0 : cmpOwnership->SetOwner(deleted.owner);
459 :
460 0 : CmpPtr<ICmpVisual> cmpVisual(sim, deleted.entityId);
461 0 : if (cmpVisual)
462 0 : cmpVisual->SetActorSeed(deleted.actorSeed);
463 : }
464 : }
465 0 : }
466 :
467 0 : void SetMovedEntitiesPosition(const std::vector<std::pair<entity_id_t, CFixedVector3D>>& movedObjects)
468 : {
469 0 : for (const std::pair<entity_id_t, CFixedVector3D>& obj : movedObjects)
470 : {
471 0 : const entity_id_t id = obj.first;
472 0 : const CFixedVector3D position = obj.second;
473 0 : CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), id);
474 0 : ENSURE(cmpPosition);
475 0 : cmpPosition->JumpTo(position.X, position.Z);
476 : }
477 0 : }
478 :
479 0 : void Do()
480 : {
481 0 : CSimulation2& sim = *g_Game->GetSimulation2();
482 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(sim, SYSTEM_ENTITY);
483 0 : ENSURE(cmpTemplateManager);
484 :
485 0 : CmpPtr<ICmpTerrain> cmpTerrain(sim, SYSTEM_ENTITY);
486 0 : if (!cmpTerrain)
487 : {
488 0 : m_OldPatches = m_NewPatches = 0;
489 0 : m_OffsetX = m_OffsetY = 0;
490 : }
491 : else
492 : {
493 0 : m_OldPatches = static_cast<ssize_t>(cmpTerrain->GetTilesPerSide() / PATCH_SIZE);
494 0 : m_NewPatches = msg->tiles / PATCH_SIZE;
495 0 : m_OffsetX = msg->offsetX / PATCH_SIZE;
496 : // Need to flip direction of vertical offset, due to screen mapping order.
497 0 : m_OffsetY = -(msg->offsetY / PATCH_SIZE);
498 :
499 0 : CTerrain* terrain = cmpTerrain->GetCTerrain();
500 0 : m_Heightmap = new u16[(m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1)];
501 0 : std::copy_n(terrain->GetHeightMap(), (m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1), m_Heightmap);
502 0 : m_Patches = new CPatch[m_OldPatches * m_OldPatches];
503 0 : for (ssize_t j = 0; j < m_OldPatches; ++j)
504 0 : for (ssize_t i = 0; i < m_OldPatches; ++i)
505 : {
506 0 : CPatch& src = *(terrain->GetPatch(i, j));
507 0 : CPatch& dst = m_Patches[j * m_OldPatches + i];
508 0 : std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]);
509 : }
510 : }
511 :
512 0 : const int radiusInTerrainUnits = m_NewPatches * PATCH_SIZE * TERRAIN_TILE_SIZE / 2 * (1.f - 1e-6f);
513 : // Opposite direction offset, as we move the destination onto the source, not the source into the destination.
514 0 : const int mapCenterX = (m_OldPatches / 2 - m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE;
515 0 : const int mapCenterZ = (m_OldPatches / 2 - m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE;
516 : // The offset to move units by is opposite the direction the map is moved, and from the corner.
517 0 : const int offsetX = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE;
518 0 : const int offsetZ = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE;
519 0 : const CFixedVector3D offset = CFixedVector3D(fixed::FromInt(offsetX), fixed::FromInt(0), fixed::FromInt(offsetZ));
520 :
521 0 : const CSimulation2::InterfaceListUnordered& ents = sim.GetEntitiesWithInterfaceUnordered(IID_Selectable);
522 0 : for (const std::pair<const entity_id_t, IComponent*>& ent : ents)
523 : {
524 0 : const entity_id_t entityId = ent.first;
525 0 : CmpPtr<ICmpPosition> cmpPosition(sim, entityId);
526 :
527 0 : if (cmpPosition && cmpPosition->IsInWorld() && Within(cmpPosition->GetPosition(), mapCenterX, mapCenterZ, radiusInTerrainUnits))
528 : {
529 0 : CFixedVector3D position = cmpPosition->GetPosition();
530 :
531 0 : m_NewPositions.emplace_back(entityId, position + offset);
532 0 : m_OldPositions.emplace_back(entityId, position);
533 : }
534 : else
535 : {
536 0 : DeletedObject deleted;
537 0 : deleted.entityId = entityId;
538 0 : deleted.templateName = cmpTemplateManager->GetCurrentTemplateName(entityId);
539 :
540 : // If the entity has a position, but the ending position is not valid;
541 0 : if (cmpPosition)
542 : {
543 0 : deleted.pos = cmpPosition->GetPosition();
544 0 : deleted.rot = cmpPosition->GetRotation();
545 : }
546 :
547 0 : CmpPtr<ICmpOwnership> cmpOwnership(sim, entityId);
548 0 : if (cmpOwnership)
549 0 : deleted.owner = cmpOwnership->GetOwner();
550 :
551 0 : CmpPtr<ICmpVisual> cmpVisual(sim, deleted.entityId);
552 0 : if (cmpVisual)
553 0 : deleted.actorSeed = cmpVisual->GetActorSeed();
554 :
555 0 : m_DeletedObjects.push_back(deleted);
556 : }
557 : }
558 :
559 0 : DeleteObjects(m_DeletedObjects);
560 0 : ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY);
561 0 : SetMovedEntitiesPosition(m_NewPositions);
562 0 : MakeDirty();
563 0 : }
564 :
565 0 : void Undo()
566 : {
567 0 : if (m_Heightmap == nullptr || m_Patches == nullptr)
568 : {
569 : // If there previously was no data, just resize to old (probably not originally valid).
570 0 : ResizeTerrain(m_OldPatches, -m_OffsetX, -m_OffsetY);
571 : }
572 : else
573 : {
574 0 : CSimulation2& sim = *g_Game->GetSimulation2();
575 0 : CmpPtr<ICmpTerrain> cmpTerrain(sim, SYSTEM_ENTITY);
576 0 : CTerrain* terrain = cmpTerrain->GetCTerrain();
577 :
578 0 : terrain->Initialize(m_OldPatches, m_Heightmap);
579 : // Copy terrain data back.
580 0 : for (ssize_t j = 0; j < m_OldPatches; ++j)
581 0 : for (ssize_t i = 0; i < m_OldPatches; ++i)
582 : {
583 0 : CPatch& src = m_Patches[j * m_OldPatches + i];
584 0 : CPatch& dst = *(terrain->GetPatch(i, j));
585 0 : std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]);
586 : }
587 : }
588 0 : RestoreObjects(m_DeletedObjects);
589 0 : SetMovedEntitiesPosition(m_OldPositions);
590 0 : MakeDirty();
591 0 : }
592 :
593 0 : void Redo()
594 : {
595 0 : DeleteObjects(m_DeletedObjects);
596 0 : ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY);
597 0 : SetMovedEntitiesPosition(m_NewPositions);
598 0 : MakeDirty();
599 0 : }
600 : };
601 0 : END_COMMAND(ResizeMap)
602 :
603 0 : QUERYHANDLER(VFSFileExists)
604 : {
605 0 : msg->exists = VfsFileExists(*msg->path);
606 0 : }
607 :
608 0 : QUERYHANDLER(VFSFileRealPath)
609 : {
610 0 : VfsPath pathname(*msg->path);
611 0 : if (pathname.empty())
612 0 : return;
613 0 : OsPath realPathname;
614 0 : if (g_VFS->GetRealPath(pathname, realPathname) == INFO::OK)
615 0 : msg->realPath = realPathname.string();
616 : }
617 :
618 0 : static Status AddToFilenames(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
619 : {
620 0 : std::vector<std::wstring>& filenames = *(std::vector<std::wstring>*)cbData;
621 0 : filenames.push_back(pathname.string().c_str());
622 0 : return INFO::OK;
623 : }
624 :
625 0 : QUERYHANDLER(GetMapList)
626 : {
627 : #define GET_FILE_LIST(path, list) \
628 : std::vector<std::wstring> list; \
629 : vfs::ForEachFile(g_VFS, path, AddToFilenames, (uintptr_t)&list, L"*.xml", vfs::DIR_RECURSIVE); \
630 : msg->list = list;
631 :
632 0 : GET_FILE_LIST(L"maps/scenarios/", scenarioFilenames);
633 0 : GET_FILE_LIST(L"maps/skirmishes/", skirmishFilenames);
634 0 : GET_FILE_LIST(L"maps/tutorials/", tutorialFilenames);
635 : #undef GET_FILE_LIST
636 0 : }
637 :
638 0 : QUERYHANDLER(GetVictoryConditionData)
639 : {
640 0 : msg->data = g_Game->GetSimulation2()->GetVictoryConditiondData();
641 0 : }
642 :
643 3 : }
|