1 /**
2 SaveScenario.c
3 Scenario saving functionality
4
5 @author Sven2
6 */
7
8 // Defines script function SaveScenarioObjects, which is called by the
9 // engine to generate the Objects.c file for scenario saving
10 // Also called for object duplication in the editor
11
12 // Temp variables used by MakeScenarioSaveName()
13 // These variables could be passed as a parameter through all saving functions instead.
14 // But that would include every single SaveScenarioObject call and associated functions and would be easy for scripters to forget.
15 static save_scenario_obj_dependencies; // Dependency graph to ensure objects are saved in proper order
16 static save_scenario_def_indices; // Used to generate unique indices in variable names
17 static save_scenario_dup_objects; // Objects to duplicate if SaveScenarioObjects is called for object duplication.
18
19 // Propert identifier of object creation
20 static const SAVEOBJ_Creation = "Creation";
21 static const SAVEOBJ_ContentsCreation = "ContentsCreation";
22 static const SAVEOBJ_ContentsCreationEx = "ContentsCreationEx";
23
SaveScenarioObjects(f,duplicate_objects)24 global func SaveScenarioObjects(f, duplicate_objects)
25 {
26 // f is a handle to the Objects.c file
27 // If called for object duplication, duplicate_objects is an array of objects to duplicate
28 save_scenario_dup_objects = duplicate_objects;
29 // Prepare props saving object
30 var props_prototype = {
31 Add = Global.SaveScenP_Add,
32 AddSet = Global.SaveScenP_AddSet,
33 AddCall = Global.SaveScenP_AddCall,
34 Remove = Global.SaveScenP_Remove,
35 RemoveCreation = Global.SaveScenP_RemoveCreation,
36 Clear = Global.SaveScenP_Clear,
37 Buffer2File = Global.SaveScenP_Buffer2File,
38 HasData = Global.SaveScenP_HasData,
39 HasCreation = Global.SaveScenP_HasCreation,
40 HasProps = Global.SaveScenP_HasProps,
41 HasProp = Global.SaveScenP_HasProp,
42 TakeProps = Global.SaveScenP_TakeProps
43 };
44 // Write all (scenario) or specified (duplication) objects!
45 var objs = duplicate_objects, obj, i;
46 if (!objs) objs = FindObjects(Find_And());
47 var n = GetLength(objs);
48 var obj_type, any_written, do_write_file = false;
49 save_scenario_def_indices = nil;
50 // In reverse order (background to foreground)
51 for (i=0; i<n/2; ++i) { obj = objs[i]; objs[i] = objs[n-i-1]; objs[n-i-1] = obj; }
52 // ...Except player crew
53 var ignore_objs = [];
54 if (!save_scenario_dup_objects)
55 {
56 for (var iplr = 0; iplr < GetPlayerCount(C4PT_User); ++iplr)
57 {
58 for (var icrew = 0, crew; crew = GetCrew(GetPlayerByIndex(iplr, C4PT_User), icrew); ++icrew)
59 {
60 ignore_objs[GetLength(ignore_objs)] = crew;
61 }
62 }
63 }
64 // Ignore objects tagged with a no-save effect
65 for (obj in objs)
66 {
67 if (GetEffect("IntNoScenarioSave", obj))
68 {
69 ignore_objs[GetLength(ignore_objs)] = obj;
70 }
71 }
72 // Write creation data and properties
73 var obj_data = SaveScen_Objects(objs, ignore_objs, props_prototype);
74 // Resolve dependencies
75 obj_data = SaveScen_ResolveDepends(objs, obj_data);
76 // Write scripts to force containers
77 obj_data = SaveScen_SetContainers(obj_data);
78 // Write header
79 FileWrite(f, "/* Automatically created objects file */\n\n");
80 // Declare static variables for objects that wish to have them
81 for (obj in objs)
82 {
83 if (obj.StaticSaveVar && !save_scenario_dup_objects)
84 {
85 if (!any_written) FileWrite(f, "static "); else FileWrite(f, ", ");
86 FileWrite(f, obj.StaticSaveVar);
87 any_written = true;
88 }
89 }
90 if (any_written)
91 {
92 FileWrite(f, ";\n\n");
93 any_written = false;
94 }
95 // Write all the creation data
96 FileWrite(f, "func InitializeObjects()\n{\n");
97 any_written = false;
98 for (obj in obj_data)
99 {
100 var spacing = " ";
101 if (obj_type != obj.o->GetID())
102 {
103 if (any_written) spacing = "\n "; // Extra spacing between different object types
104 obj_type = obj.o->GetID();
105 any_written = false;
106 }
107 if (obj.o.StaticSaveVar && !save_scenario_dup_objects)
108 {
109 if (obj.props->HasCreation()) FileWrite(f, Format("%s%s = ", spacing, obj.o.StaticSaveVar));
110 }
111 else if (obj.write_label)
112 {
113 FileWrite(f, Format("%svar %s", spacing, obj.o->MakeScenarioSaveName()));
114 if (obj.props->HasCreation()) FileWrite(f, " = "); else FileWrite(f, ";\n");
115 }
116 else if (obj.props->HasCreation())
117 {
118 FileWrite(f, spacing);
119 }
120 if (obj.props->~Buffer2File(f)) do_write_file = any_written = true;
121 }
122 // Write global effects
123 if (!save_scenario_dup_objects)
124 {
125 any_written = false;
126 var fx; i=0;
127 while (fx = GetEffect("*", nil, i++))
128 {
129 var fx_buffer = {Prototype=props_prototype};
130 EffectCall(nil, fx, "SaveScen", fx_buffer);
131 if (fx_buffer->HasData())
132 {
133 if (!any_written && do_write_file) FileWrite(f, " \n");
134 any_written = do_write_file = true;
135 fx_buffer->~Buffer2File(f);
136 }
137 }
138 }
139 // Cleanup
140 save_scenario_def_indices = save_scenario_obj_dependencies = save_scenario_dup_objects = nil;
141 // Write footer
142 FileWrite(f, " return true;\n}\n");
143 // Done; success. Return true if any objects or effects were written to the file.
144 return do_write_file;
145 }
146
SaveScen_Objects(array objs,array ignore_objs,proplist props_prototype)147 global func SaveScen_Objects(array objs, array ignore_objs, proplist props_prototype)
148 {
149 // Write all object data into buffers
150 var n = GetLength(objs);
151 var obj_data = CreateArray(n), obj;
152 for (var i=0; i<n; ++i)
153 {
154 obj = objs[i];
155 obj_data[i] = { o=obj, props={Prototype=props_prototype} };
156 obj._save_scen_objdata = obj_data[i];
157 }
158 for (var i=0; i<n; ++i)
159 {
160 obj = objs[i];
161 // Skip objects on ignore list (check for all objects up the containment chain)
162 var is_ignored = false;
163 var container_obj = obj;
164 while (container_obj)
165 {
166 if (GetIndexOf(ignore_objs, container_obj) >= 0) is_ignored = true;
167 if (WildcardMatch(Format("%i", container_obj->GetID()), "GUI_*")) is_ignored = true; // big bunch of objects that should not be stored.
168 container_obj = container_obj->Contained();
169 }
170 if (is_ignored) continue;
171 // Generate object creation and property strings
172 save_scenario_obj_dependencies = [];
173 if (!obj->SaveScenarioObject(obj_data[i].props))
174 {
175 obj_data[i].props->Clear();
176 continue;
177 }
178 if (obj->Contained()) obj->Contained()->MakeScenarioSaveName(); // force container dependency
179 if (obj_data[i].props->HasProps()) obj_data[i].write_label = true;
180 obj_data[i].dependencies = save_scenario_obj_dependencies;
181 obj_data[i].n_dependencies = GetLength(save_scenario_obj_dependencies);
182 save_scenario_obj_dependencies = nil;
183 }
184 return obj_data;
185 }
186
SaveScen_ResolveDepends(array objs,array obj_data)187 global func SaveScen_ResolveDepends(array objs, array obj_data)
188 {
189 // Dependency pointer from obj to obj_data
190 var i,j,k,n=GetLength(objs),od;
191 for (i=0; i<n; ++i)
192 {
193 for (j=0; j<obj_data[i].n_dependencies; ++j)
194 {
195 k = GetIndexOf(objs, obj_data[i].dependencies[j]);
196 if (k < 0 || obj_data[i].dependencies[j] == obj_data[i].o) obj_data[i].dependencies[j] = nil; else obj_data[i].dependencies[j] = obj_data[k];
197 }
198 if (objs[i]->Contained())
199 {
200 k = GetIndexOf(objs, objs[i]->Contained());
201 if (k >= 0) obj_data[i].co = obj_data[k];
202 }
203 }
204 // Resolve dependencies
205 k = 0;
206 for (i=0; i<n; ++i)
207 {
208 k = Max(k, i);
209 od = obj_data[i];
210 while (od.i_dep_resolved < od.n_dependencies)
211 {
212 var dep = od.dependencies[od.i_dep_resolved++];
213 if (dep)
214 {
215 j = GetIndexOf(obj_data, dep);
216 if (j > i)
217 {
218 // The dependent object is behind us in the list. This is bad!
219 if (!od.props->HasCreation())
220 {
221 // This is just object properties. Move them behind dependent object creation
222 var obj_data_new = CreateArray(n);
223 obj_data_new[0:i] = obj_data[0:i];
224 obj_data_new[i:j] = obj_data[i+1:j+1];
225 obj_data_new[j] = obj_data[i];
226 if (j<n-1) obj_data_new[j+1:n] = obj_data[j+1:n];
227 obj_data = obj_data_new;
228 if (j <= k) --k;
229 --i; break;
230 }
231 else if (j <= k)
232 {
233 // Circular dependency. Detach object property setting from object creation.
234 var obj_data_new = CreateArray(++n);
235 obj_data_new[0:j+1] = obj_data[0:j+1];
236 obj_data_new[j+1] = { o=od.o, co=od.co, dependencies=od.dependencies, n_dependencies=od.n_dependencies, i_dep_resolved=od.i_dep_resolved, props=od.props->TakeProps() };
237 od.n_dependencies = 0; od.props_detached = true;
238 if (j<n-1) obj_data_new[j+2:n] = obj_data[j+1:n];
239 obj_data = obj_data_new;
240 ++k; break;
241 }
242 else
243 {
244 // No circular dependency. Just move object we depend on in front of us and process its dependencies.
245 var obj_data_new = CreateArray(n);
246 if (i) obj_data_new[0:i] = obj_data[0:i];
247 obj_data_new[i] = obj_data[j];
248 obj_data_new[i+1:j+1] = obj_data[i:j];
249 if (j<n-1) obj_data_new[j+1:n] = obj_data[j+1:n];
250 obj_data = obj_data_new;
251 ++k; --i; break;
252 }
253 }
254 }
255 }
256 }
257 // Free up all dependency data
258 for (od in obj_data)
259 {
260 od.dependencies = nil;
261 od.o._save_scen_objdata = nil;
262 }
263 return obj_data;
264 }
265
SaveScen_SetContainers(array obj_data)266 global func SaveScen_SetContainers(array obj_data)
267 {
268 // Ensure that objects are in proper container
269 // Replace calls to CreateObjectAbove with CreateContents.
270 for (var obj in obj_data) if (obj.o->Contained())
271 {
272 // ignore if container object was not saved
273 if (obj.co && obj.co.props->HasCreation())
274 {
275 // creation and props? Then turn into CreateContents
276 if (obj.props->HasProp(SAVEOBJ_ContentsCreation) && !obj.props_detached)
277 {
278 obj.props->Remove(SAVEOBJ_Creation);
279 // Adjust owner if necessery
280 if (obj.o->GetOwner() != obj.o->Contained()->GetOwner())
281 obj.props->AddCall("Owner", obj.o, "SetOwner", obj.o->GetOwner());
282 }
283 else if (obj.props.origin)
284 {
285 // props detached from creation. Use Enter() call to enter container
286 // the label must have been written because something depended on the object.
287 obj.props.origin->Remove(SAVEOBJ_ContentsCreation);
288 obj.props.origin->Remove(SAVEOBJ_ContentsCreationEx);
289 obj.props->AddCall("Container", obj.o, "Enter", obj.o->Contained());
290 // Ensure layer is written for detached contents if it is the same as the container
291 var o_layer = obj.o->GetObjectLayer();
292 if (o_layer && o_layer == obj.co->GetObjectLayer()) obj.props->AddCall("Layer", obj.o, "SetObjectLayer", o_layer);
293 }
294 }
295 else
296 {
297 // unsaved container - just create object outside
298 obj.props->Remove(SAVEOBJ_ContentsCreation);
299 obj.props->Remove(SAVEOBJ_ContentsCreationEx);
300 }
301 }
302 // Concatenate multiple contents creations
303 var cont;
304 for (var obj in obj_data) if ((cont = obj.o->Contained())) if (obj.props->HasProp(SAVEOBJ_ContentsCreationEx))
305 {
306 var num_contents_concat = 1;
307 if ((!obj.o.StaticSaveVar || save_scenario_dup_objects) && !obj.write_label)
308 {
309 for (var obj2 in obj_data) if (obj2 != obj && obj2.o->Contained() == cont && obj.o->GetID() == obj2.o->GetID() && obj2.props->HasProp(SAVEOBJ_ContentsCreationEx))
310 {
311 ++num_contents_concat;
312 obj2.props->Clear();
313 }
314 }
315 if (num_contents_concat > 1)
316 {
317 obj.props->Remove(SAVEOBJ_ContentsCreation);
318 var creation_prop = obj.props->HasProp(SAVEOBJ_ContentsCreationEx);
319 creation_prop.s = Format(creation_prop.s, num_contents_concat);
320 }
321 else
322 obj.props->Remove(SAVEOBJ_ContentsCreationEx);
323 }
324 return obj_data;
325 }
326
AddScenarioSaveDependency()327 global func AddScenarioSaveDependency()
328 {
329 // Remember this object in the list of dependencies for the currently processed object
330 if (save_scenario_obj_dependencies && GetIndexOf(save_scenario_obj_dependencies, this)<0) save_scenario_obj_dependencies[GetLength(save_scenario_obj_dependencies)] = this;
331 }
332
333 // documented in /docs/sdk/script/fn
MakeScenarioSaveName()334 global func MakeScenarioSaveName()
335 {
336 // Get name to be used to store this object in a scenario
337 if (!this) FatalError("MakeScenarioSaveName needs definition or object context!");
338 // Definitions may just use their regular name
339 if (this.Prototype == Global) return Format("%i", this);
340 // Duplication mode: If this is an object that is not being duplicated, just reference it as Object(number)
341 if (save_scenario_dup_objects && GetIndexOf(save_scenario_dup_objects, this)<0) return Format("%v", this);
342 // When the name is queried while properties are built, it means that there is a dependency. Store it.
343 AddScenarioSaveDependency();
344 // Write name if it had been used elsewhere
345 if (this._save_scen_objdata) this._save_scen_objdata.write_label = true;
346 // Build actual name using unique number (unless there's a static save variable name for us)
347 if (this.StaticSaveVar && !save_scenario_dup_objects) return this.StaticSaveVar;
348 if (!save_scenario_def_indices) save_scenario_def_indices = {};
349 var base_name = Format("%i", GetID());
350 if (base_name == "") base_name = "Unknown";
351 // save_scenario_def_indices is a proplist containing arrays of all objects sorted by type
352 // the saved name is <ID><index>, where index is the 1-based index into the array
353 var def_indices = save_scenario_def_indices[base_name];
354 var idx;
355 if (!def_indices)
356 {
357 save_scenario_def_indices[base_name] = def_indices = [this];
358 idx = 0;
359 }
360 else
361 {
362 idx = GetIndexOf(def_indices, this);
363 if (idx<0)
364 {
365 idx = GetLength(def_indices);
366 def_indices[idx] = this;
367 }
368 }
369 return Format("%s%03d", base_name, idx+1);
370 }
371
SaveScenarioObject(props)372 global func SaveScenarioObject(props)
373 {
374 // Called in object context: Default object writing procedure
375 // Overwrite this method and return false for objects that should not be saved
376 // Overwrite and call inherited for objects that add/remove/alter default creation/properties
377 var owner_string = "", i;
378 if (GetOwner() != NO_OWNER) owner_string = Format(", %d", GetOwner());
379 // Object creation: Default is to create above bottom center point
380 // This usually works well with stuff like buildings that may change size in updated versions
381 // The center point is usually what's of interest for rotated objects, contained objects and InEarth material as well as some objects that explicitely state different creation
382 var is_centered_creation = (!this.SaveScenarioCreateFromBottom) && (GetR() || Contained() || GBackSolid() || (GetCategory() & (C4D_Rule | C4D_Goal | C4D_Environment)) || this.SaveScenarioCreateCentered);
383 if (is_centered_creation)
384 {
385 if (!GetX() && !GetY() && (owner_string == ""))
386 props->Add(SAVEOBJ_Creation, "CreateObject(%i)", GetID()); // super-short version for e.g. goals/rules at position 0/0
387 else
388 props->Add(SAVEOBJ_Creation, "CreateObject(%i, %d, %d%s)", GetID(), GetX(), GetY(), owner_string);
389 }
390 else
391 {
392 props->Add(SAVEOBJ_Creation, "CreateObjectAbove(%i, %d, %d%s)", GetID(), GetX(), GetDefBottom(), owner_string);
393 }
394 // Contained creation is added alongside regular creation because it is not yet known if CreateObject+Enter or CreateContents can be used due to dependencies.
395 // func SaveScen_SetContainers will take care of removing one of the two creation strings after dependencies have been resolved.
396 if (Contained())
397 {
398 props->Add(SAVEOBJ_ContentsCreation, "%s->CreateContents(%i)", Contained()->MakeScenarioSaveName(), GetID());
399 props->Add(SAVEOBJ_ContentsCreationEx, "%s->CreateContents(%i, %%d)", Contained()->MakeScenarioSaveName(), GetID());
400 }
401 // Write some default props every object should save
402 var v, is_static = (GetCategory() & C4D_StaticBack) || Contained(), def = GetID();
403 v = GetAlive(); if (!v && (GetCategory()&C4D_Living)) props->AddCall("Alive", this, "Kill", this, true);
404 v = GetDir(); if (v) props->AddCall("Dir", this, "SetDir", GetConstantNameByValueSafe(v,"DIR_"));
405 v = GetComDir(); if (v) props->AddCall("ComDir", this, "SetComDir", GetConstantNameByValueSafe(v,"COMD_"));
406 v = GetCon(); if (v != 100) props->AddCall("Con", this, "SetCon", Max(v,1));
407 v = GetCategory(); if (v != def->GetCategory()) props->AddCall("Category", this, "SetCategory", GetBitmaskNameByValue(v, "C4D_"));
408 v = GetR(); if (v && !Contained()) props->AddCall("R", this, "SetR", v);
409 v = GetXDir(); if (v && !is_static) props->AddCall("XDir", this, "SetXDir", v);
410 v = GetYDir(); if (v && !is_static) if (!Inside(v, 1,12) || !GetContact(-1, CNAT_Bottom))
411 props->AddCall("YDir", this, "SetYDir", v); // consolidate small YDir for standing objects
412 v = GetRDir(); if (v && !is_static) props->AddCall("RDir", this, "SetRDir", v);
413 var default_color = 0xffffffff;
414 if (GetDefColorByOwner()) if (GetOwner() == NO_OWNER) default_color = 0xff; else default_color = GetPlayerColor(GetOwner());
415 v = GetColor(); if (v && v != default_color) props->AddCall("Color", this, "SetColor", Format("0x%x", v));
416 v = GetClrModulation(); if (v && v != 0xffffffff) props->AddCall("ClrModulation", this, "SetClrModulation", Format("0x%08x", v));
417 v = GetObjectBlitMode();if (v) props->AddCall("BlitMode", this, "SetObjectBlitMode", GetBitmaskNameByValue(v & ~GFX_BLIT_Custom, "GFX_BLIT_"));
418 for (i=0; v=def->GetMeshMaterial(i); ++i)
419 if (GetMeshMaterial(i) != v) props->AddCall("MeshMaterial", this, "SetMeshMaterial", Format("%v", GetMeshMaterial(i)), i);
420 v = this.Name; if (v != def.Name) props->AddCall("Name", this, "SetName", SaveScenarioValue2String(v));
421 v = this.MaxEnergy; if (v != def.MaxEnergy) props->AddSet ("MaxEnergy", this, "MaxEnergy", this.MaxEnergy);
422 v = GetEnergy(); if (v != def.MaxEnergy/1000) props->AddCall("Energy", this, "DoEnergy", v-def.MaxEnergy/1000);
423 v = this.Visibility; if (v != def.Visibility) props->AddSet ("Visibility", this, "Visibility", SaveScenarioValue2String(v, "VIS_", true));
424 v = this.Plane; if (v != def.Plane) props->AddSet ("Plane", this, "Plane", v);
425 v = GetObjectLayer(); var def_layer=nil; if (Contained()) def_layer = Contained()->GetObjectLayer();
426 if (v != def_layer) props->AddCall("Layer", this, "SetObjectLayer", v);
427 v = this.LineColors; if (v != def.LineColors) props->AddSet ("LineColors", this, "LineColors", v);
428 v = this.StaticSaveVar; if (v && !save_scenario_dup_objects) props->AddSet ("StaticSaveVar", this, "StaticSaveVar", Format("%v", v)); // do not duplicate StaticSaveVar because it needs to be unique
429 // Commands: Could store the whole command stack using AppendCommand.
430 // However, usually there is one base command and the rest is derived
431 // (e.g.: A Get command may lead to multiple MoveTo commands to the
432 // target object). So just store the topmost command.
433 var command, last_command; i=0;
434 while (command = GetCommand(0, i++)) last_command = command;
435 if (last_command)
436 {
437 i-=2;
438 props->AddCall("Command", this, "SetCommand", Format("%v", last_command),
439 SaveScenarioValue2String(GetCommand(1,i)), // target
440 SaveScenarioValue2String(GetCommand(2,i)), // x
441 SaveScenarioValue2String(GetCommand(3,i)), // y
442 SaveScenarioValue2String(GetCommand(4,i)), // target2
443 SaveScenarioValue2String(GetCommand(5,i))); // data
444 }
445 // Effects
446 var fx; i=0;
447 while (fx = GetEffect("*", this, i++)) EffectCall(this, fx, "SaveScen", props);
448 // EditorProps
449 if (this.EditorProps)
450 {
451 var all_prop_names = GetProperties(this.EditorProps), prop_name, prop;
452 for (prop_name in all_prop_names)
453 {
454 if ((prop=this.EditorProps[prop_name]))
455 {
456 if (GetType(prop) == C4V_PropList)
457 {
458 if (prop.Save)
459 {
460 v = this[prop_name];
461 var default_v = GetID()[prop_name];
462 if (!DeepEqual(v, default_v))
463 {
464 if (prop.Set)
465 {
466 // Save as call
467 props->AddCall(prop.Save, this, prop.Set, SaveScenarioValue2String(v));
468 }
469 else
470 {
471 // Save as direct value setting
472 props->AddSet(prop.Save, this, prop_name, SaveScenarioValue2String(v));
473 }
474 }
475 }
476 }
477 }
478 }
479 }
480 // A con of != 100 and a non-zero rotation may have moved the object, if so re-center it after setting the con and rotation.
481 if (is_centered_creation && (GetCon != 100 || (GetR() && !Contained())))
482 props->AddCall("SetPosition", this, "SetPosition", GetX(), GetY());
483 // Automatic unsticking for items lying on the ground and for animals / clonks
484 // Do this after Con/Rotation and other calls that may affect the shape
485 // (Note: If someone loads a game in paused mode and immediately saves without unpausing, most unstick calls for items will be lost)
486 if (!Contained() && !this.SaveScenarioCreateCentered && !this.SaveScenarioCreateFromBottom && !Stuck() && (GetAlive() || (this.Collectible && GetContact(-1, CNAT_Left | CNAT_Right | CNAT_Top | CNAT_Bottom))))
487 {
488 var unstick_range = 7; // GetScenMapZoom() - 1; // Unfortunately, this would not be sync save for network clients doing runtime join on editor sessions [end then reloading from a runtime save]
489 props->AddCall("Unstick", this, "Unstick", unstick_range);
490 }
491 // Initialization function as late as possible
492 v = this.CustomInitializationScript; if (v) props->AddCall("CustomInitialization", this, "CustomInitialize", Format("%v", v));
493 return true;
494 }
495
496 // documented in /docs/sdk/script/fn
SaveScenarioObjectAction(props)497 global func SaveScenarioObjectAction(props)
498 {
499 // Helper function to store action properties
500 var v;
501 props->AddCall("Action", this, "SetAction", Format("%v", GetAction() ?? "Idle"), GetActionTarget(), GetActionTarget(1));
502 if (v = GetPhase()) props->AddCall("Phase", this, "SetPhase", v);
503 return true;
504 }
505
506
FxFireSaveScen(object obj,proplist fx,proplist props)507 global func FxFireSaveScen(object obj, proplist fx, proplist props)
508 {
509 // this is burning. Save incineration to scenario.
510 props->AddCall("Fire", obj, "Incinerate", fx.strength, fx.caused_by, fx.blasted, fx.incinerating_object);
511 return true;
512 }
513
514
515 /* Helper functions for value formatting */
516
517 // Helper function to turn values of several types into a strings to be written to Objects.c
SaveScenarioValue2String(v,string constant_prefix,bool allow_bitmask)518 global func SaveScenarioValue2String(v, string constant_prefix, bool allow_bitmask)
519 {
520 var rval, vt = GetType(v);
521 if (vt == C4V_C4Object) return v->MakeScenarioSaveName();
522 if (vt == C4V_Array) // save procedure for arrays: recurse into contents (cannot save arrays pointing into itself that way)
523 {
524 for (var el in v)
525 {
526 if (rval) rval = Format("%s,%s", rval, SaveScenarioValue2String(el, constant_prefix, allow_bitmask));
527 else rval = SaveScenarioValue2String(el, constant_prefix, allow_bitmask);
528 constant_prefix = nil; // Only first element is actual bitmask (at least or VIS_, and that's the only user for this case)
529 }
530 if (rval) rval = Format("[%s]", rval); else rval = "[]";
531 return rval;
532 }
533 // custom save procedure for some prop lists or definitions
534 if (vt == C4V_PropList || vt == C4V_Def)
535 {
536 rval = v->~ToString();
537 if (rval) return rval;
538 // proplist saving
539 if (vt == C4V_PropList)
540 {
541 var props = GetProperties(v);
542 for (var el in props)
543 {
544 if (GetChar(el) == GetChar("_")) continue; // props starting with underscore are not to be saved
545 if (rval) rval = Format("%s,%s=%s", rval, el, SaveScenarioValue2String(v[el]));
546 else rval = Format("%s=%s", el, SaveScenarioValue2String(v[el]));
547 }
548 if (rval) rval = Format("{%s}", rval); else rval = "{}";
549 return rval;
550 }
551 }
552 // int as constant? (treat nil as 0 in this case)
553 if (constant_prefix)
554 if (allow_bitmask)
555 return GetBitmaskNameByValue(v, constant_prefix);
556 else
557 return GetConstantNameByValueSafe(v, constant_prefix);
558 // Strings need to be quoted and escaped
559 if (vt == C4V_String)
560 {
561 return Format("\"%s\"", ReplaceString(ReplaceString(v, "\\", "\\\\"), "\"", "\\\""));
562 }
563 // Otherwise, rely on the default %v formatting
564 return Format("%v", v);
565 }
566
GetBitmaskNameByValue(v,prefix)567 global func GetBitmaskNameByValue(v, prefix)
568 {
569 // Compose bitmask of names of individual bits
570 // e.g. GetBitmaskNameByValue(3, "C4D_") == "C4D_StaticBack|C4D_Structure"
571 var s, n=0;
572 for (var i=0; i<31 && v; ++i)
573 {
574 var v2 = 1<<i;
575 if (v & v2)
576 {
577 v &= ~v2;
578 var s2 = GetConstantNameByValue(v2, prefix);
579 if (s2) s2 = Format("%s%s", prefix, s2); else s2 = Format("%x", v2);
580 if (s) s = Format("%s|%s", s, s2); else s = s2;
581 }
582 }
583 return s ?? GetConstantNameByValueSafe(0, prefix);
584 }
585
GetConstantNameByValueSafe(v,prefix)586 global func GetConstantNameByValueSafe(v, prefix)
587 {
588 // Get constant name by value including prefix. If not found, return number as string
589 var s = GetConstantNameByValue(v, prefix);
590 if (s) return Format("%s%s", prefix, s); else return Format("%d", v);
591 }
592
593
594 /* SaveScen_PropList functions */
595 // I would like to use non-global here, but how can I take a pointer then?
596
SaveScenP_Add(string name,string s,...)597 global func SaveScenP_Add(string name, string s, ...)
598 {
599 // apply format parametrers
600 s = Format(s, ...);
601 // just append to array of strings
602 // could build a string using data=Format("%s%s",data,s); here - however, then we'd be limited to some internal buffer sizes
603 var new_data = {name=name, s=s};
604 if (!this.data) this.data=[new_data]; else this.data[GetLength(this.data)] = new_data;
605 return true;
606 }
607
SaveScenP_Remove(string name)608 global func SaveScenP_Remove(string name)
609 {
610 // return all lines identified by name. return number of lines removed
611 var idx, n=0, n_data;
612 if (this.data)
613 {
614 n_data = GetLength(this.data);
615 while (true)
616 {
617 idx = -1;
618 while (++idx<n_data) if (this.data[idx].name == name) break;
619 if (idx == n_data) break;
620 ++n;
621 if (!--n_data) { this.data=nil; break; }
622 if (idx < n_data) this.data[idx] = this.data[n_data];
623 SetLength(this.data, n_data);
624 }
625 }
626 return n;
627 }
628
SaveScenP_RemoveCreation()629 global func SaveScenP_RemoveCreation()
630 {
631 // Remove any creation strings
632 return this->Remove(SAVEOBJ_ContentsCreation) + this->Remove(SAVEOBJ_ContentsCreationEx) + this->Remove(SAVEOBJ_Creation);
633 }
634
SaveScenP_Clear()635 global func SaveScenP_Clear()
636 {
637 // Remove all property and creation data
638 this.data = nil;
639 return true;
640 }
641
SaveScenP_AddCall(string name,proplist obj,string set_fn,...)642 global func SaveScenP_AddCall(string name, proplist obj, string set_fn, ...)
643 {
644 // add string of style Obj123->SetFoo(bar, baz, ...)
645 // string parameters will not be quoted, so the caller can do some parameter formatting
646 // compose parameter string
647 var max_pars = 10, last_written = 2, set_pars = "", n_pars = 0;
648 for (var i=3; i<max_pars; ++i)
649 {
650 var par = Par(i);
651 var par_type = GetType(par);
652 if (par_type)
653 {
654 while (++last_written < i)
655 if (n_pars++)
656 set_pars = Format("%s%s", set_pars, ", nil");
657 else
658 set_pars = "nil";
659 if (par_type != C4V_String) par = SaveScenarioValue2String(par);
660 if (n_pars++)
661 set_pars = Format("%s, %s", set_pars, par);
662 else
663 set_pars = par;
664 }
665 }
666 // add setter
667 return this->Add(name, "%s->%s(%s)", obj->MakeScenarioSaveName(), set_fn, set_pars);
668 }
669
SaveScenP_AddSet(string name,object obj,string local_name,value)670 global func SaveScenP_AddSet(string name, object obj, string local_name, value)
671 {
672 // add string of style Obj123->local_name = value
673 // string parameters will not be quoted, so the caller can do some parameter formatting
674 if (GetType(value) != C4V_String) value = SaveScenarioValue2String(value);
675 return this->Add(name, "%s.%s = %s", obj->MakeScenarioSaveName(), local_name, value);
676 }
677
SaveScenP_Buffer2File(f)678 global func SaveScenP_Buffer2File(f)
679 {
680 // buffer.data is an array of strings to be written to file f
681 if (!this.data) return false;
682 var i=0, indent = "";
683 for (var creation in [true, false])
684 {
685 for (var v in this.data)
686 {
687 var v_is_creation = ((v.name == SAVEOBJ_Creation) || (v.name == SAVEOBJ_ContentsCreation) || (v.name == SAVEOBJ_ContentsCreationEx));
688 if (v_is_creation != creation) continue;
689 if (i || !creation) indent = " ";
690 FileWrite(f, Format("%s%s;\n", indent, v.s));
691 ++i;
692 }
693 }
694 return i;
695 }
696
SaveScenP_HasData()697 global func SaveScenP_HasData()
698 {
699 // Anything added to data array?
700 return !!this.data;
701 }
702
SaveScenP_HasCreation()703 global func SaveScenP_HasCreation()
704 {
705 // Functions to test whether any creation data has been added
706 if (!this.data) return false;
707 for (var v in this.data) if ((v.name == SAVEOBJ_Creation) || (v.name == SAVEOBJ_ContentsCreation) || (v.name == SAVEOBJ_ContentsCreationEx)) return true;
708 return false;
709 }
710
SaveScenP_HasProps()711 global func SaveScenP_HasProps()
712 {
713 // Functions to test whether any property data has been added
714 if (!this.data) return false;
715 for (var v in this.data) if ((v.name != SAVEOBJ_Creation) && (v.name != SAVEOBJ_ContentsCreation) && (v.name != SAVEOBJ_ContentsCreationEx)) return true;
716 return false;
717 }
718
SaveScenP_HasProp(string prop)719 global func SaveScenP_HasProp(string prop)
720 {
721 // Test if specific prop is present
722 if (!this.data) return nil;
723 for (var v in this.data) if (v.name == prop) return v;
724 return nil;
725 }
726
SaveScenP_TakeProps()727 global func SaveScenP_TakeProps()
728 {
729 // Remove all props from this data and add them to a copy of this
730 var result = { Prototype = this.Prototype };
731 if (this.data)
732 {
733 var creation = nil, props = nil;
734 for (var v in this.data)
735 if ((v.name != SAVEOBJ_Creation) && (v.name != SAVEOBJ_ContentsCreation) && (v.name != SAVEOBJ_ContentsCreationEx))
736 if (!props) props = [v]; else props[GetLength(props)] = v;
737 else
738 if (!creation) creation = [v]; else creation[GetLength(creation)] = v;
739 this.data = creation;
740 result.data = props;
741 result.origin = this;
742 }
743 return result;
744 }
745
CustomInitialize(string script)746 global func CustomInitialize(string script)
747 {
748 // run a custom object initialization and
749 return eval(this.CustomInitializationScript = script);
750 }
751