1 /*
2 * mape - C4 Landscape.txt editor
3 *
4 * Copyright (c) 2005-2009, Armin Burgmeier
5 *
6 * Distributed under the terms of the ISC license; see accompanying file
7 * "COPYING" for details.
8 *
9 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10 * See accompanying file "TRADEMARK" for details.
11 *
12 * To redistribute this file separately, substitute the full license texts
13 * for the above references.
14 */
15
16 #include "C4Include.h"
17 #include "landscape/C4MapScript.h"
18 #include "landscape/C4MapCreatorS2.h"
19 #include "script/C4ScriptHost.h"
20 #include "object/C4DefList.h"
21 #include "object/C4Def.h"
22 #include "script/C4Aul.h"
23 #include "lib/StdMeshLoader.h"
24
25 #include "mape/cpp-handles/material-handle.h"
26 #include "mape/cpp-handles/texture-handle.h"
27 #include "mape/cpp-handles/log-handle.h"
28 #include "mape/cpp-handles/mapgen-handle.h"
29
30 #define HANDLE_TO_MATERIAL_MAP(handle) (reinterpret_cast<C4MaterialMap*>(handle))
31 #define HANDLE_TO_TEXTURE_MAP(handle) (reinterpret_cast<C4TextureMap*>(handle))
32 #define HANDLE_TO_GROUP(handle) (reinterpret_cast<C4Group*>(handle))
33
34 namespace
35 {
36
HasAlgoScript(C4MCNode * node)37 bool HasAlgoScript(C4MCNode* node)
38 {
39 if(node->Type() == MCN_Overlay && static_cast<C4MCOverlay*>(node)->Algorithm == static_cast<C4MCOverlay*>(node)->GetAlgo("script"))
40 return true;
41
42 if(node->Child0) return HasAlgoScript(node->Child0);
43 if(node->Next) return HasAlgoScript(node->Next);
44 return false;
45 }
46
47 class FakeSkeletonLoader: public StdMeshSkeletonLoader
48 {
49 public:
GetSkeletonByDefinition(const char * definition) const50 virtual StdMeshSkeleton* GetSkeletonByDefinition(const char* definition) const { return nullptr; }
51 };
52
53 }
54
55 extern "C" {
56
57 struct _C4MapgenHandle {
58 unsigned int width;
59 unsigned int height;
60 unsigned int rowstride;
61 StdCopyStrBuf error_message;
62 BYTE* data;
63 };
64
c4_mapgen_handle_init_script_engine()65 void c4_mapgen_handle_init_script_engine()
66 {
67 InitCoreFunctionMap(&ScriptEngine);
68 ::MapScript.InitFunctionMap(&ScriptEngine);
69 }
70
c4_mapgen_handle_deinit_script_engine()71 void c4_mapgen_handle_deinit_script_engine()
72 {
73 MapScript.Clear();
74 GameScript.Clear();
75 ScriptEngine.Clear();
76 }
77
c4_mapgen_handle_set_map_library(C4GroupHandle * group_handle)78 void c4_mapgen_handle_set_map_library(C4GroupHandle* group_handle)
79 {
80 ::Definitions.Clear();
81
82 C4Def* libmap = new C4Def;
83 libmap->id = C4ID(std::string("Library_Map"));
84 libmap->SetName(libmap->id.ToString());
85 libmap->Category = C4D_StaticBack;
86 FakeSkeletonLoader loader;
87 if(!libmap->Load(*HANDLE_TO_GROUP(group_handle), loader, C4D_Load_Script, nullptr, nullptr))
88 {
89 fprintf(stderr, "Failed to load Library_Map script\n");
90 delete libmap;
91 }
92 else
93 {
94 ::Definitions.Add(libmap, false);
95 }
96 }
97
c4_mapgen_handle_new_script(const char * filename,const char * source,C4MaterialMapHandle * material_map,C4TextureMapHandle * texture_map,unsigned int map_width,unsigned int map_height)98 C4MapgenHandle* c4_mapgen_handle_new_script(const char* filename, const char* source, C4MaterialMapHandle* material_map, C4TextureMapHandle* texture_map, unsigned int map_width, unsigned int map_height)
99 {
100 // Re-initialize script engine. Otherwise, we get a warning when the user
101 // changes the value of a constant, since it is defined already from the
102 // previous map rendering. Note that we do not need to re-load the map library.
103 c4_mapgen_handle_deinit_script_engine();
104 c4_mapgen_handle_init_script_engine();
105
106 try
107 {
108 // TODO: Could also re-use an existing CSurface8,
109 // saving unnecessary malloc/free between map renderings
110 C4SLandscape landscape;
111 landscape.Default();
112
113 landscape.MapWdt.Set(map_width, 0, map_width, map_width);
114 landscape.MapHgt.Set(map_height, 0, map_height, map_height);
115 landscape.MapPlayerExtend = 0;
116
117 c4_log_handle_clear();
118 ::MapScript.LoadData(filename, source, nullptr);
119 // If InitializeMap() returns false, the map creator wants to
120 // call a fallback in the scenario script. This crashes if no
121 // scenario script is loaded, so simply load an empty script
122 // here:
123 ::GameScript.LoadData("Script.c", "", nullptr);
124
125 const char* parse_error = c4_log_handle_get_first_log_message();
126 if(parse_error)
127 throw std::runtime_error(parse_error);
128
129 // Link script engine (resolve includes/appends, generate code)
130 c4_log_handle_clear();
131 ScriptEngine.Link(&::Definitions);
132 if(c4_log_handle_get_n_log_messages() > 1)
133 throw std::runtime_error(c4_log_handle_get_first_log_message());
134
135 // Generate map, fail if return error occurs
136 c4_log_handle_clear();
137 std::unique_ptr<CSurface8> out_ptr_fg, out_ptr_bg;
138 const bool result = ::MapScript.InitializeMap(
139 &landscape,
140 HANDLE_TO_TEXTURE_MAP(texture_map),
141 HANDLE_TO_MATERIAL_MAP(material_map),
142 1,
143 &out_ptr_fg, &out_ptr_bg);
144
145 // Don't show any map if there was a script runtime error
146 const char* runtime_error = c4_log_handle_get_first_log_message();
147 if(runtime_error)
148 throw std::runtime_error(runtime_error);
149
150 if(!result)
151 throw std::runtime_error("No InitializeMap() function present in the script, or it returns false");
152
153 C4MapgenHandle* handle = new C4MapgenHandle;
154 handle->width = out_ptr_fg->Wdt;
155 handle->height = out_ptr_fg->Hgt;
156 handle->rowstride = out_ptr_fg->Wdt;
157 handle->error_message = nullptr;
158 handle->data = out_ptr_fg->Bits;
159 out_ptr_fg->ReleaseBuffer();
160
161 return handle;
162 }
163 catch(const std::exception& ex)
164 {
165 C4MapgenHandle* handle = new C4MapgenHandle;
166 handle->width = 0;
167 handle->height = 0;
168 handle->error_message.Copy(ex.what());
169 handle->data = nullptr;
170 return handle;
171 }
172 }
173
c4_mapgen_handle_new(const char * filename,const char * source,const char * script_path,C4MaterialMapHandle * material_map,C4TextureMapHandle * texture_map,unsigned int map_width,unsigned int map_height)174 C4MapgenHandle* c4_mapgen_handle_new(const char* filename, const char* source, const char* script_path, C4MaterialMapHandle* material_map, C4TextureMapHandle* texture_map, unsigned int map_width, unsigned int map_height)
175 {
176 try
177 {
178 C4SLandscape landscape;
179 landscape.Default();
180
181 landscape.MapWdt.Set(map_width, 0, map_width, map_width);
182 landscape.MapHgt.Set(map_height, 0, map_height, map_height);
183 landscape.MapPlayerExtend = 0;
184
185 C4MapCreatorS2 mapgen(
186 &landscape,
187 HANDLE_TO_TEXTURE_MAP(texture_map),
188 HANDLE_TO_MATERIAL_MAP(material_map),
189 1
190 );
191
192 C4MCParser parser(&mapgen);
193 parser.ParseMemFile(source, filename);
194
195 C4MCMap* map = mapgen.GetMap(nullptr);
196 if(!map) throw std::runtime_error("No map definition in source file");
197
198 // Setup the script engine if there is an algo=script overlay in the
199 // Landscape.txt file
200 if(HasAlgoScript(mapgen.GetMap(nullptr)))
201 {
202 // Re-initialize script engine. Otherwise, we get a warning when the user
203 // changes the value of a constant, since it is defined already from the
204 // previous map rendering. Note that we do not need to re-load the map library.
205 c4_mapgen_handle_deinit_script_engine();
206 c4_mapgen_handle_init_script_engine();
207
208 if(script_path == nullptr)
209 throw std::runtime_error("For algo=script overlays to work, save the file first at the location of the Script.c file");
210
211 gchar* dirname = g_path_get_dirname(script_path);
212 gchar* basename = g_path_get_basename(script_path);
213
214 C4Group File;
215 if(!File.Open(dirname))
216 {
217 StdStrBuf error_msg = FormatString("Failed to open directory '%s': %s", dirname, File.GetError());
218 g_free(dirname);
219 g_free(basename);
220 throw std::runtime_error(error_msg.getData());
221 }
222
223 // get scripts
224 File.ResetSearch();
225 if(!File.FindNextEntry(basename, (char*)nullptr))
226 {
227 g_free(dirname);
228 g_free(basename);
229 StdStrBuf error_msg = FormatString("Failed to load '%s': No such file", script_path);
230 throw std::runtime_error(error_msg.getData());
231 }
232
233 c4_log_handle_clear();
234 GameScript.Load(File, basename, nullptr, nullptr);
235 g_free(dirname);
236 g_free(basename);
237
238 const char* parse_error = c4_log_handle_get_first_log_message();
239 if(parse_error)
240 throw std::runtime_error(parse_error);
241
242 // Link script engine (resolve includes/appends, generate code)
243 c4_log_handle_clear();
244 ScriptEngine.Link(&::Definitions);
245 if(c4_log_handle_get_n_log_messages() > 1)
246 throw std::runtime_error(c4_log_handle_get_first_log_message());
247 }
248
249 c4_log_handle_clear();
250 int32_t out_width, out_height;
251 BYTE* array = mapgen.RenderBuf(nullptr, out_width, out_height);
252
253 // Don't show any map if there was a script runtime error
254 const char* runtime_error = c4_log_handle_get_first_log_message();
255 if(runtime_error)
256 {
257 delete[] array;
258 throw std::runtime_error(runtime_error);
259 }
260
261 C4MapgenHandle* handle = new C4MapgenHandle;
262 handle->width = map_width;
263 handle->height = map_height;
264 handle->rowstride = out_width;
265 handle->error_message = nullptr;
266 handle->data = array;
267 return handle;
268 }
269 catch(const C4MCParserErr& err)
270 {
271 C4MapgenHandle* handle = new C4MapgenHandle;
272 handle->width = 0;
273 handle->height = 0;
274 handle->error_message.Copy(err.Msg);
275 handle->data = nullptr;
276 return handle;
277 }
278 catch(const std::exception& ex)
279 {
280 C4MapgenHandle* handle = new C4MapgenHandle;
281 handle->width = 0;
282 handle->height = 0;
283 handle->error_message.Copy(ex.what());
284 handle->data = nullptr;
285 return handle;
286 }
287 }
288
c4_mapgen_handle_free(C4MapgenHandle * mapgen)289 void c4_mapgen_handle_free(C4MapgenHandle* mapgen)
290 {
291 delete[] mapgen->data;
292 delete mapgen;
293 }
294
c4_mapgen_handle_get_map(C4MapgenHandle * mapgen)295 const unsigned char* c4_mapgen_handle_get_map(C4MapgenHandle* mapgen)
296 {
297 return reinterpret_cast<unsigned char*>(mapgen->data);
298 }
299
c4_mapgen_handle_get_width(C4MapgenHandle * mapgen)300 unsigned int c4_mapgen_handle_get_width(C4MapgenHandle* mapgen)
301 {
302 assert(mapgen->data != nullptr);
303 return mapgen->width;
304 }
305
c4_mapgen_handle_get_height(C4MapgenHandle * mapgen)306 unsigned int c4_mapgen_handle_get_height(C4MapgenHandle* mapgen)
307 {
308 assert(mapgen->data != nullptr);
309 return mapgen->height;
310 }
311
c4_mapgen_handle_get_rowstride(C4MapgenHandle * mapgen)312 unsigned int c4_mapgen_handle_get_rowstride(C4MapgenHandle* mapgen)
313 {
314 assert(mapgen->data != nullptr);
315 return mapgen->rowstride;
316 }
317
c4_mapgen_handle_get_error(C4MapgenHandle * mapgen)318 const char* c4_mapgen_handle_get_error(C4MapgenHandle* mapgen)
319 {
320 if(mapgen->data != nullptr)
321 return nullptr;
322 return mapgen->error_message.getData();
323 }
324
325 } // extern "C"
326