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