2017-06-02 15:14:52 +00:00
/**
SaveScenario . c
Scenario saving functionality
@ author Sven2
*/
2013-12-22 22:47:40 +00:00
// Defines script function SaveScenarioObjects, which is called by the
// engine to generate the Objects.c file for scenario saving
2016-06-20 05:41:15 +00:00
// Also called for object duplication in the editor
2013-12-22 22:47:40 +00:00
2016-06-20 05:41:15 +00:00
// Temp variables used by MakeScenarioSaveName()
// These variables could be passed as a parameter through all saving functions instead.
// But that would include every single SaveScenarioObject call and associated functions and would be easy for scripters to forget.
static save_scenario_obj_dependencies ; // Dependency graph to ensure objects are saved in proper order
static save_scenario_def_indices ; // Used to generate unique indices in variable names
static save_scenario_dup_objects ; // Objects to duplicate if SaveScenarioObjects is called for object duplication.
2014-09-22 16:49:05 +00:00
2013-12-22 22:47:40 +00:00
// Propert identifier of object creation
static const SAVEOBJ_Creation = " Creation " ;
static const SAVEOBJ_ContentsCreation = " ContentsCreation " ;
2015-01-18 13:27:32 +00:00
static const SAVEOBJ_ContentsCreationEx = " ContentsCreationEx " ;
2013-12-22 22:47:40 +00:00
2016-06-20 05:41:15 +00:00
global func SaveScenarioObjects ( f , duplicate_objects )
2013-12-22 22:47:40 +00:00
{
// f is a handle to the Objects.c file
2016-06-20 05:41:15 +00:00
// If called for object duplication, duplicate_objects is an array of objects to duplicate
save_scenario_dup_objects = duplicate_objects ;
2013-12-22 22:47:40 +00:00
// Prepare props saving object
var props_prototype = {
Add = Global . SaveScenP_Add ,
AddSet = Global . SaveScenP_AddSet ,
AddCall = Global . SaveScenP_AddCall ,
Remove = Global . SaveScenP_Remove ,
RemoveCreation = Global . SaveScenP_RemoveCreation ,
Clear = Global . SaveScenP_Clear ,
Buffer2File = Global . SaveScenP_Buffer2File ,
HasData = Global . SaveScenP_HasData ,
HasCreation = Global . SaveScenP_HasCreation ,
HasProps = Global . SaveScenP_HasProps ,
HasProp = Global . SaveScenP_HasProp ,
TakeProps = Global . SaveScenP_TakeProps
} ;
2016-06-20 05:41:15 +00:00
// Write all (scenario) or specified (duplication) objects!
var objs = duplicate_objects , obj , i ;
if ( ! objs ) objs = FindObjects ( Find_And ( ) ) ;
2013-12-22 22:47:40 +00:00
var n = GetLength ( objs ) ;
var obj_type , any_written , do_write_file = false ;
2014-09-22 16:49:05 +00:00
save_scenario_def_indices = nil ;
2013-12-22 22:47:40 +00:00
// In reverse order (background to foreground)
for ( i = 0 ; i < n / 2 ; + + i ) { obj = objs [ i ] ; objs [ i ] = objs [ n - i - 1 ] ; objs [ n - i - 1 ] = obj ; }
// ...Except player crew
var ignore_objs = [ ] ;
2016-06-20 05:41:15 +00:00
if ( ! save_scenario_dup_objects )
2017-02-14 06:35:40 +00:00
{
2016-06-20 05:41:15 +00:00
for ( var iplr = 0 ; iplr < GetPlayerCount ( C4PT_User ) ; + + iplr )
2017-02-14 06:35:40 +00:00
{
2016-06-20 05:41:15 +00:00
for ( var icrew = 0 , crew ; crew = GetCrew ( GetPlayerByIndex ( iplr , C4PT_User ) , icrew ) ; + + icrew )
2017-02-14 06:35:40 +00:00
{
2016-06-20 05:41:15 +00:00
ignore_objs [ GetLength ( ignore_objs ) ] = crew ;
2017-02-14 06:35:40 +00:00
}
}
}
// Ignore objects tagged with a no-save effect
for ( obj in objs )
{
if ( GetEffect ( " IntNoScenarioSave " , obj ) )
{
ignore_objs [ GetLength ( ignore_objs ) ] = obj ;
}
}
2013-12-22 22:47:40 +00:00
// Write creation data and properties
var obj_data = SaveScen_Objects ( objs , ignore_objs , props_prototype ) ;
// Resolve dependencies
obj_data = SaveScen_ResolveDepends ( objs , obj_data ) ;
// Write scripts to force containers
obj_data = SaveScen_SetContainers ( obj_data ) ;
// Write header
FileWrite ( f , " /* Automatically created objects file */ \n \n " ) ;
// Declare static variables for objects that wish to have them
for ( obj in objs )
2017-02-14 06:35:40 +00:00
{
2016-06-20 05:41:15 +00:00
if ( obj . StaticSaveVar & & ! save_scenario_dup_objects )
2013-12-22 22:47:40 +00:00
{
if ( ! any_written ) FileWrite ( f , " static " ) ; else FileWrite ( f , " , " ) ;
FileWrite ( f , obj . StaticSaveVar ) ;
any_written = true ;
}
2017-02-14 06:35:40 +00:00
}
2013-12-22 22:47:40 +00:00
if ( any_written )
{
FileWrite ( f , " ; \n \n " ) ;
any_written = false ;
}
// Write all the creation data
FileWrite ( f , " func InitializeObjects() \n { \n " ) ;
any_written = false ;
for ( obj in obj_data )
{
2015-01-17 16:00:12 +00:00
var spacing = " " ;
2013-12-22 22:47:40 +00:00
if ( obj_type ! = obj . o - > GetID ( ) )
{
2015-01-17 16:00:12 +00:00
if ( any_written ) spacing = " \n " ; // Extra spacing between different object types
2013-12-22 22:47:40 +00:00
obj_type = obj . o - > GetID ( ) ;
any_written = false ;
}
2016-06-20 05:41:15 +00:00
if ( obj . o . StaticSaveVar & & ! save_scenario_dup_objects )
2013-12-22 22:47:40 +00:00
{
2015-01-17 16:00:12 +00:00
if ( obj . props - > HasCreation ( ) ) FileWrite ( f , Format ( " %s%s = " , spacing , obj . o . StaticSaveVar ) ) ;
2013-12-22 22:47:40 +00:00
}
else if ( obj . write_label )
{
2015-01-17 16:00:12 +00:00
FileWrite ( f , Format ( " %svar %s " , spacing , obj . o - > MakeScenarioSaveName ( ) ) ) ;
2013-12-22 22:47:40 +00:00
if ( obj . props - > HasCreation ( ) ) FileWrite ( f , " = " ) ; else FileWrite ( f , " ; \n " ) ;
}
else if ( obj . props - > HasCreation ( ) )
{
2015-01-17 16:00:12 +00:00
FileWrite ( f , spacing ) ;
2013-12-22 22:47:40 +00:00
}
if ( obj . props - > ~ Buffer2File ( f ) ) do_write_file = any_written = true ;
}
// Write global effects
2016-06-20 05:41:15 +00:00
if ( ! save_scenario_dup_objects )
2013-12-22 22:47:40 +00:00
{
2016-06-20 05:41:15 +00:00
any_written = false ;
var fx ; i = 0 ;
while ( fx = GetEffect ( " * " , nil , i + + ) )
2013-12-22 22:47:40 +00:00
{
2016-06-20 05:41:15 +00:00
var fx_buffer = { Prototype = props_prototype } ;
EffectCall ( nil , fx , " SaveScen " , fx_buffer ) ;
if ( fx_buffer - > HasData ( ) )
{
if ( ! any_written & & do_write_file ) FileWrite ( f , " \n " ) ;
any_written = do_write_file = true ;
fx_buffer - > ~ Buffer2File ( f ) ;
}
2013-12-22 22:47:40 +00:00
}
}
2014-09-22 16:49:05 +00:00
// Cleanup
2016-06-20 05:41:15 +00:00
save_scenario_def_indices = save_scenario_obj_dependencies = save_scenario_dup_objects = nil ;
2013-12-22 22:47:40 +00:00
// Write footer
FileWrite ( f , " return true; \n } \n " ) ;
// Done; success. Return true if any objects or effects were written to the file.
return do_write_file ;
}
global func SaveScen_Objects ( array objs , array ignore_objs , proplist props_prototype )
{
// Write all object data into buffers
var n = GetLength ( objs ) ;
var obj_data = CreateArray ( n ) , obj ;
for ( var i = 0 ; i < n ; + + i )
{
obj = objs [ i ] ;
obj_data [ i ] = { o = obj , props = { Prototype = props_prototype } } ;
2016-08-05 04:30:43 +00:00
obj . _save_scen_objdata = obj_data [ i ] ;
}
for ( var i = 0 ; i < n ; + + i )
{
obj = objs [ i ] ;
2016-12-27 14:08:30 +00:00
// Skip objects on ignore list (check for all objects up the containment chain)
var is_ignored = false ;
var container_obj = obj ;
while ( container_obj )
{
if ( GetIndexOf ( ignore_objs , container_obj ) > = 0 ) is_ignored = true ;
if ( WildcardMatch ( Format ( " %i " , container_obj - > GetID ( ) ) , " GUI_* " ) ) is_ignored = true ; // big bunch of objects that should not be stored.
container_obj = container_obj - > Contained ( ) ;
}
if ( is_ignored ) continue ;
2013-12-22 22:47:40 +00:00
// Generate object creation and property strings
save_scenario_obj_dependencies = [ ] ;
if ( ! obj - > SaveScenarioObject ( obj_data [ i ] . props ) )
{
obj_data [ i ] . props - > Clear ( ) ;
continue ;
}
if ( obj - > Contained ( ) ) obj - > Contained ( ) - > MakeScenarioSaveName ( ) ; // force container dependency
if ( obj_data [ i ] . props - > HasProps ( ) ) obj_data [ i ] . write_label = true ;
obj_data [ i ] . dependencies = save_scenario_obj_dependencies ;
obj_data [ i ] . n_dependencies = GetLength ( save_scenario_obj_dependencies ) ;
save_scenario_obj_dependencies = nil ;
}
return obj_data ;
}
global func SaveScen_ResolveDepends ( array objs , array obj_data )
{
// Dependency pointer from obj to obj_data
var i , j , k , n = GetLength ( objs ) , od ;
for ( i = 0 ; i < n ; + + i )
{
for ( j = 0 ; j < obj_data [ i ] . n_dependencies ; + + j )
{
k = GetIndexOf ( objs , obj_data [ i ] . dependencies [ j ] ) ;
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 ] ;
}
if ( objs [ i ] - > Contained ( ) )
{
k = GetIndexOf ( objs , objs [ i ] - > Contained ( ) ) ;
if ( k > = 0 ) obj_data [ i ] . co = obj_data [ k ] ;
}
}
// Resolve dependencies
k = 0 ;
for ( i = 0 ; i < n ; + + i )
{
k = Max ( k , i ) ;
od = obj_data [ i ] ;
while ( od . i_dep_resolved < od . n_dependencies )
{
var dep = od . dependencies [ od . i_dep_resolved + + ] ;
if ( dep )
{
j = GetIndexOf ( obj_data , dep ) ;
if ( j > i )
{
// The dependent object is behind us in the list. This is bad!
if ( ! od . props - > HasCreation ( ) )
{
// This is just object properties. Move them behind dependent object creation
var obj_data_new = CreateArray ( n ) ;
obj_data_new [ 0 : i ] = obj_data [ 0 : i ] ;
obj_data_new [ i : j ] = obj_data [ i + 1 : j + 1 ] ;
obj_data_new [ j ] = obj_data [ i ] ;
if ( j < n - 1 ) obj_data_new [ j + 1 : n ] = obj_data [ j + 1 : n ] ;
obj_data = obj_data_new ;
if ( j < = k ) - - k ;
- - i ; break ;
}
else if ( j < = k )
{
// Circular dependency. Detach object property setting from object creation.
var obj_data_new = CreateArray ( + + n ) ;
obj_data_new [ 0 : j + 1 ] = obj_data [ 0 : j + 1 ] ;
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 ( ) } ;
od . n_dependencies = 0 ; od . props_detached = true ;
if ( j < n - 1 ) obj_data_new [ j + 2 : n ] = obj_data [ j + 1 : n ] ;
obj_data = obj_data_new ;
+ + k ; break ;
}
else
{
// No circular dependency. Just move object we depend on in front of us and process its dependencies.
var obj_data_new = CreateArray ( n ) ;
if ( i ) obj_data_new [ 0 : i ] = obj_data [ 0 : i ] ;
obj_data_new [ i ] = obj_data [ j ] ;
obj_data_new [ i + 1 : j + 1 ] = obj_data [ i : j ] ;
if ( j < n - 1 ) obj_data_new [ j + 1 : n ] = obj_data [ j + 1 : n ] ;
obj_data = obj_data_new ;
+ + k ; - - i ; break ;
}
}
}
}
}
// Free up all dependency data
2016-08-05 04:30:43 +00:00
for ( od in obj_data )
{
od . dependencies = nil ;
od . o . _save_scen_objdata = nil ;
}
2013-12-22 22:47:40 +00:00
return obj_data ;
}
global func SaveScen_SetContainers ( array obj_data )
{
// Ensure that objects are in proper container
2015-01-10 09:14:02 +00:00
// Replace calls to CreateObjectAbove with CreateContents.
2013-12-22 22:47:40 +00:00
for ( var obj in obj_data ) if ( obj . o - > Contained ( ) )
{
// ignore if container object was not saved
if ( obj . co & & obj . co . props - > HasCreation ( ) )
{
// creation and props? Then turn into CreateContents
if ( obj . props - > HasProp ( SAVEOBJ_ContentsCreation ) & & ! obj . props_detached )
{
obj . props - > Remove ( SAVEOBJ_Creation ) ;
// Adjust owner if necessery
if ( obj . o - > GetOwner ( ) ! = obj . o - > Contained ( ) - > GetOwner ( ) )
obj . props - > AddCall ( " Owner " , obj . o , " SetOwner " , obj . o - > GetOwner ( ) ) ;
}
else if ( obj . props . origin )
{
// props detached from creation. Use Enter() call to enter container
// the label must have been written because something depended on the object.
obj . props . origin - > Remove ( SAVEOBJ_ContentsCreation ) ;
2015-01-18 13:27:32 +00:00
obj . props . origin - > Remove ( SAVEOBJ_ContentsCreationEx ) ;
2013-12-22 22:47:40 +00:00
obj . props - > AddCall ( " Container " , obj . o , " Enter " , obj . o - > Contained ( ) ) ;
2014-08-14 11:43:23 +00:00
// Ensure layer is written for detached contents if it is the same as the container
var o_layer = obj . o - > GetObjectLayer ( ) ;
if ( o_layer & & o_layer = = obj . co - > GetObjectLayer ( ) ) obj . props - > AddCall ( " Layer " , obj . o , " SetObjectLayer " , o_layer ) ;
2013-12-22 22:47:40 +00:00
}
}
else
{
// unsaved container - just create object outside
obj . props - > Remove ( SAVEOBJ_ContentsCreation ) ;
2015-01-18 13:27:32 +00:00
obj . props - > Remove ( SAVEOBJ_ContentsCreationEx ) ;
2013-12-22 22:47:40 +00:00
}
}
2015-01-18 13:27:32 +00:00
// Concatenate multiple contents creations
2018-04-09 09:53:55 +00:00
for ( var obj in obj_data )
if ( obj . o - > Contained ( ) & & obj . props - > HasProp ( SAVEOBJ_ContentsCreationEx ) )
2015-01-18 13:27:32 +00:00
{
2018-04-09 09:53:55 +00:00
var num_contents_concat = 1 ;
if ( ( ! obj . o . StaticSaveVar | | save_scenario_dup_objects ) & & ! obj . write_label )
2015-01-18 13:27:32 +00:00
{
2018-04-09 09:53:55 +00:00
for ( var obj2 in obj_data ) if ( obj2 ! = obj & & obj2 . o - > Contained ( ) = = obj . o - > Contained ( ) & & obj . o - > GetID ( ) = = obj2 . o - > GetID ( ) & & obj2 . props - > HasProp ( SAVEOBJ_ContentsCreationEx ) )
{
+ + num_contents_concat ;
obj2 . props - > Clear ( ) ;
}
2015-01-18 13:27:32 +00:00
}
2018-04-09 09:53:55 +00:00
if ( num_contents_concat > 1 )
{
obj . props - > Remove ( SAVEOBJ_ContentsCreation ) ;
var creation_prop = obj . props - > HasProp ( SAVEOBJ_ContentsCreationEx ) ;
creation_prop . s = Format ( creation_prop . s , num_contents_concat ) ;
}
else
obj . props - > Remove ( SAVEOBJ_ContentsCreationEx ) ;
2015-01-18 13:27:32 +00:00
}
2013-12-22 22:47:40 +00:00
return obj_data ;
}
2016-08-05 04:30:43 +00:00
global func AddScenarioSaveDependency ( )
{
// Remember this object in the list of dependencies for the currently processed object
if ( save_scenario_obj_dependencies & & GetIndexOf ( save_scenario_obj_dependencies , this ) < 0 ) save_scenario_obj_dependencies [ GetLength ( save_scenario_obj_dependencies ) ] = this ;
}
2017-10-20 22:10:42 +00:00
// documented in /docs/sdk/script/fn
2013-12-22 22:47:40 +00:00
global func MakeScenarioSaveName ( )
{
// Get name to be used to store this object in a scenario
if ( ! this ) FatalError ( " MakeScenarioSaveName needs definition or object context! " ) ;
// Definitions may just use their regular name
if ( this . Prototype = = Global ) return Format ( " %i " , this ) ;
2016-06-20 05:41:15 +00:00
// Duplication mode: If this is an object that is not being duplicated, just reference it as Object(number)
if ( save_scenario_dup_objects & & GetIndexOf ( save_scenario_dup_objects , this ) < 0 ) return Format ( " %v " , this ) ;
2013-12-22 22:47:40 +00:00
// When the name is queried while properties are built, it means that there is a dependency. Store it.
2016-08-05 04:30:43 +00:00
AddScenarioSaveDependency ( ) ;
// Write name if it had been used elsewhere
if ( this . _save_scen_objdata ) this . _save_scen_objdata . write_label = true ;
2013-12-22 22:47:40 +00:00
// Build actual name using unique number (unless there's a static save variable name for us)
2016-06-20 05:41:15 +00:00
if ( this . StaticSaveVar & & ! save_scenario_dup_objects ) return this . StaticSaveVar ;
2014-09-22 16:49:05 +00:00
if ( ! save_scenario_def_indices ) save_scenario_def_indices = { } ;
var base_name = Format ( " %i " , GetID ( ) ) ;
if ( base_name = = " " ) base_name = " Unknown " ;
// save_scenario_def_indices is a proplist containing arrays of all objects sorted by type
// the saved name is <ID><index>, where index is the 1-based index into the array
var def_indices = save_scenario_def_indices [ base_name ] ;
var idx ;
if ( ! def_indices )
{
save_scenario_def_indices [ base_name ] = def_indices = [ this ] ;
idx = 0 ;
}
else
{
idx = GetIndexOf ( def_indices , this ) ;
if ( idx < 0 )
{
idx = GetLength ( def_indices ) ;
def_indices [ idx ] = this ;
}
}
return Format ( " %s%03d " , base_name , idx + 1 ) ;
2013-12-22 22:47:40 +00:00
}
global func SaveScenarioObject ( props )
{
// Called in object context: Default object writing procedure
// Overwrite this method and return false for objects that should not be saved
// Overwrite and call inherited for objects that add/remove/alter default creation/properties
2014-04-18 12:20:10 +00:00
var owner_string = " " , i ;
2013-12-22 22:47:40 +00:00
if ( GetOwner ( ) ! = NO_OWNER ) owner_string = Format ( " , %d " , GetOwner ( ) ) ;
2015-01-17 16:00:12 +00:00
// Object creation: Default is to create above bottom center point
// This usually works well with stuff like buildings that may change size in updated versions
// 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
var is_centered_creation = ( ! this . SaveScenarioCreateFromBottom ) & & ( GetR ( ) | | Contained ( ) | | GBackSolid ( ) | | ( GetCategory ( ) & ( C4D_Rule | C4D_Goal | C4D_Environment ) ) | | this . SaveScenarioCreateCentered ) ;
if ( is_centered_creation )
{
if ( ! GetX ( ) & & ! GetY ( ) & & ( owner_string = = " " ) )
props - > Add ( SAVEOBJ_Creation , " CreateObject(%i) " , GetID ( ) ) ; // super-short version for e.g. goals/rules at position 0/0
else
props - > Add ( SAVEOBJ_Creation , " CreateObject(%i, %d, %d%s) " , GetID ( ) , GetX ( ) , GetY ( ) , owner_string ) ;
}
else
2016-09-04 22:25:16 +00:00
{
2015-01-17 16:00:12 +00:00
props - > Add ( SAVEOBJ_Creation , " CreateObjectAbove(%i, %d, %d%s) " , GetID ( ) , GetX ( ) , GetDefBottom ( ) , owner_string ) ;
2016-09-04 22:25:16 +00:00
}
2015-01-18 13:27:32 +00:00
// Contained creation is added alongside regular creation because it is not yet known if CreateObject+Enter or CreateContents can be used due to dependencies.
2013-12-22 22:47:40 +00:00
// func SaveScen_SetContainers will take care of removing one of the two creation strings after dependencies have been resolved.
2015-01-18 13:27:32 +00:00
if ( Contained ( ) )
{
props - > Add ( SAVEOBJ_ContentsCreation , " %s->CreateContents(%i) " , Contained ( ) - > MakeScenarioSaveName ( ) , GetID ( ) ) ;
props - > Add ( SAVEOBJ_ContentsCreationEx , " %s->CreateContents(%i, %%d) " , Contained ( ) - > MakeScenarioSaveName ( ) , GetID ( ) ) ;
}
2013-12-22 22:47:40 +00:00
// Write some default props every object should save
var v , is_static = ( GetCategory ( ) & C4D_StaticBack ) | | Contained ( ) , def = GetID ( ) ;
v = GetAlive ( ) ; if ( ! v & & ( GetCategory ( ) & C4D_Living ) ) props - > AddCall ( " Alive " , this , " Kill " , this , true ) ;
v = GetDir ( ) ; if ( v ) props - > AddCall ( " Dir " , this , " SetDir " , GetConstantNameByValueSafe ( v , " DIR_ " ) ) ;
v = GetComDir ( ) ; if ( v ) props - > AddCall ( " ComDir " , this , " SetComDir " , GetConstantNameByValueSafe ( v , " COMD_ " ) ) ;
v = GetCon ( ) ; if ( v ! = 100 ) props - > AddCall ( " Con " , this , " SetCon " , Max ( v , 1 ) ) ;
v = GetCategory ( ) ; if ( v ! = def - > GetCategory ( ) ) props - > AddCall ( " Category " , this , " SetCategory " , GetBitmaskNameByValue ( v , " C4D_ " ) ) ;
2015-01-17 16:00:12 +00:00
v = GetR ( ) ; if ( v & & ! Contained ( ) ) props - > AddCall ( " R " , this , " SetR " , v ) ;
2013-12-22 22:47:40 +00:00
v = GetXDir ( ) ; if ( v & & ! is_static ) props - > AddCall ( " XDir " , this , " SetXDir " , v ) ;
v = GetYDir ( ) ; if ( v & & ! is_static ) if ( ! Inside ( v , 1 , 12 ) | | ! GetContact ( - 1 , CNAT_Bottom ) )
props - > AddCall ( " YDir " , this , " SetYDir " , v ) ; // consolidate small YDir for standing objects
v = GetRDir ( ) ; if ( v & & ! is_static ) props - > AddCall ( " RDir " , this , " SetRDir " , v ) ;
2015-01-17 16:00:12 +00:00
var default_color = 0xffffffff ;
if ( GetDefColorByOwner ( ) ) if ( GetOwner ( ) = = NO_OWNER ) default_color = 0xff ; else default_color = GetPlayerColor ( GetOwner ( ) ) ;
v = GetColor ( ) ; if ( v & & v ! = default_color ) props - > AddCall ( " Color " , this , " SetColor " , Format ( " 0x%x " , v ) ) ;
2013-12-22 22:47:40 +00:00
v = GetClrModulation ( ) ; if ( v & & v ! = 0xffffffff ) props - > AddCall ( " ClrModulation " , this , " SetClrModulation " , Format ( " 0x%08x " , v ) ) ;
v = GetObjectBlitMode ( ) ; if ( v ) props - > AddCall ( " BlitMode " , this , " SetObjectBlitMode " , GetBitmaskNameByValue ( v & ~ GFX_BLIT_Custom , " GFX_BLIT_ " ) ) ;
2014-04-18 12:20:10 +00:00
for ( i = 0 ; v = def - > GetMeshMaterial ( i ) ; + + i )
if ( GetMeshMaterial ( i ) ! = v ) props - > AddCall ( " MeshMaterial " , this , " SetMeshMaterial " , Format ( " %v " , GetMeshMaterial ( i ) ) , i ) ;
2016-10-03 16:07:49 +00:00
v = this . Name ; if ( v ! = def . Name ) props - > AddCall ( " Name " , this , " SetName " , SaveScenarioValue2String ( v ) ) ;
2013-12-22 22:47:40 +00:00
v = this . MaxEnergy ; if ( v ! = def . MaxEnergy ) props - > AddSet ( " MaxEnergy " , this , " MaxEnergy " , this . MaxEnergy ) ;
v = GetEnergy ( ) ; if ( v ! = def . MaxEnergy / 1000 ) props - > AddCall ( " Energy " , this , " DoEnergy " , v - def . MaxEnergy / 1000 ) ;
2016-02-07 18:20:50 +00:00
v = this . Visibility ; if ( v ! = def . Visibility ) props - > AddSet ( " Visibility " , this , " Visibility " , SaveScenarioValue2String ( v , " VIS_ " , true ) ) ;
2013-12-22 22:47:40 +00:00
v = this . Plane ; if ( v ! = def . Plane ) props - > AddSet ( " Plane " , this , " Plane " , v ) ;
2014-08-14 11:43:23 +00:00
v = GetObjectLayer ( ) ; var def_layer = nil ; if ( Contained ( ) ) def_layer = Contained ( ) - > GetObjectLayer ( ) ;
if ( v ! = def_layer ) props - > AddCall ( " Layer " , this , " SetObjectLayer " , v ) ;
2016-10-03 16:07:49 +00:00
v = this . LineColors ; if ( v ! = def . LineColors ) props - > AddSet ( " LineColors " , this , " LineColors " , v ) ;
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
2013-12-22 22:47:40 +00:00
// Commands: Could store the whole command stack using AppendCommand.
// However, usually there is one base command and the rest is derived
// (e.g.: A Get command may lead to multiple MoveTo commands to the
// target object). So just store the topmost command.
2014-04-18 12:20:10 +00:00
var command , last_command ; i = 0 ;
2013-12-22 22:47:40 +00:00
while ( command = GetCommand ( 0 , i + + ) ) last_command = command ;
if ( last_command )
{
i - = 2 ;
props - > AddCall ( " Command " , this , " SetCommand " , Format ( " %v " , last_command ) ,
SaveScenarioValue2String ( GetCommand ( 1 , i ) ) , // target
SaveScenarioValue2String ( GetCommand ( 2 , i ) ) , // x
SaveScenarioValue2String ( GetCommand ( 3 , i ) ) , // y
SaveScenarioValue2String ( GetCommand ( 4 , i ) ) , // target2
SaveScenarioValue2String ( GetCommand ( 5 , i ) ) ) ; // data
}
// Effects
var fx ; i = 0 ;
while ( fx = GetEffect ( " * " , this , i + + ) ) EffectCall ( this , fx , " SaveScen " , props ) ;
2016-08-06 18:49:48 +00:00
// EditorProps
if ( this . EditorProps )
{
2016-08-20 05:22:15 +00:00
var all_prop_names = GetProperties ( this . EditorProps ) , prop_name , prop ;
2016-08-06 18:49:48 +00:00
for ( prop_name in all_prop_names )
{
2018-07-23 07:11:51 +00:00
if ( ( prop = this . EditorProps [ prop_name ] ) ! = nil )
2016-08-06 18:49:48 +00:00
{
if ( GetType ( prop ) = = C4V_PropList )
{
2016-08-20 05:22:15 +00:00
if ( prop . Save )
2016-08-06 18:49:48 +00:00
{
2016-08-20 05:22:15 +00:00
v = this [ prop_name ] ;
var default_v = GetID ( ) [ prop_name ] ;
if ( ! DeepEqual ( v , default_v ) )
{
if ( prop . Set )
{
// Save as call
props - > AddCall ( prop . Save , this , prop . Set , SaveScenarioValue2String ( v ) ) ;
}
else
{
// Save as direct value setting
props - > AddSet ( prop . Save , this , prop_name , SaveScenarioValue2String ( v ) ) ;
}
}
2016-08-06 18:49:48 +00:00
}
}
}
}
}
2017-03-26 19:15:05 +00:00
// 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.
if ( is_centered_creation & & ( GetCon ! = 100 | | ( GetR ( ) & & ! Contained ( ) ) ) )
props - > AddCall ( " SetPosition " , this , " SetPosition " , GetX ( ) , GetY ( ) ) ;
2016-09-04 22:25:16 +00:00
// Automatic unsticking for items lying on the ground and for animals / clonks
// Do this after Con/Rotation and other calls that may affect the shape
// (Note: If someone loads a game in paused mode and immediately saves without unpausing, most unstick calls for items will be lost)
if ( ! Contained ( ) & & ! this . SaveScenarioCreateCentered & & ! this . SaveScenarioCreateFromBottom & & ! Stuck ( ) & & ( GetAlive ( ) | | ( this . Collectible & & GetContact ( - 1 , CNAT_Left | CNAT_Right | CNAT_Top | CNAT_Bottom ) ) ) )
{
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]
props - > AddCall ( " Unstick " , this , " Unstick " , unstick_range ) ;
}
2016-08-25 04:18:11 +00:00
// Initialization function as late as possible
v = this . CustomInitializationScript ; if ( v ) props - > AddCall ( " CustomInitialization " , this , " CustomInitialize " , Format ( " %v " , v ) ) ;
2013-12-22 22:47:40 +00:00
return true ;
}
2017-10-20 22:10:42 +00:00
// documented in /docs/sdk/script/fn
2013-12-22 22:47:40 +00:00
global func SaveScenarioObjectAction ( props )
{
// Helper function to store action properties
props - > AddCall ( " Action " , this , " SetAction " , Format ( " %v " , GetAction ( ) ? ? " Idle " ) , GetActionTarget ( ) , GetActionTarget ( 1 ) ) ;
2018-07-23 07:11:51 +00:00
var v = GetPhase ( ) ;
if ( v )
props - > AddCall ( " Phase " , this , " SetPhase " , v ) ;
2013-12-22 22:47:40 +00:00
return true ;
}
global func FxFireSaveScen ( object obj , proplist fx , proplist props )
{
// this is burning. Save incineration to scenario.
props - > AddCall ( " Fire " , obj , " Incinerate " , fx . strength , fx . caused_by , fx . blasted , fx . incinerating_object ) ;
return true ;
}
/* Helper functions for value formatting */
// Helper function to turn values of several types into a strings to be written to Objects.c
2016-02-07 18:20:50 +00:00
global func SaveScenarioValue2String ( v , string constant_prefix , bool allow_bitmask )
2013-12-22 22:47:40 +00:00
{
2016-07-13 05:45:29 +00:00
var rval , vt = GetType ( v ) ;
if ( vt = = C4V_C4Object ) return v - > MakeScenarioSaveName ( ) ;
if ( vt = = C4V_Array ) // save procedure for arrays: recurse into contents (cannot save arrays pointing into itself that way)
2014-01-22 19:28:41 +00:00
{
for ( var el in v )
{
2016-02-07 18:20:50 +00:00
if ( rval ) rval = Format ( " %s,%s " , rval , SaveScenarioValue2String ( el , constant_prefix , allow_bitmask ) ) ;
else rval = SaveScenarioValue2String ( el , constant_prefix , allow_bitmask ) ;
constant_prefix = nil ; // Only first element is actual bitmask (at least or VIS_, and that's the only user for this case)
2014-01-22 19:28:41 +00:00
}
if ( rval ) rval = Format ( " [%s] " , rval ) ; else rval = " [] " ;
return rval ;
}
2016-07-13 05:45:29 +00:00
// custom save procedure for some prop lists or definitions
if ( vt = = C4V_PropList | | vt = = C4V_Def )
2014-01-22 19:28:41 +00:00
{
rval = v - > ~ ToString ( ) ;
if ( rval ) return rval ;
2016-07-13 05:45:29 +00:00
// proplist saving
if ( vt = = C4V_PropList )
{
var props = GetProperties ( v ) ;
for ( var el in props )
{
2016-07-13 21:39:36 +00:00
if ( GetChar ( el ) = = GetChar ( " _ " ) ) continue ; // props starting with underscore are not to be saved
2016-07-13 05:45:29 +00:00
if ( rval ) rval = Format ( " %s,%s=%s " , rval , el , SaveScenarioValue2String ( v [ el ] ) ) ;
else rval = Format ( " %s=%s " , el , SaveScenarioValue2String ( v [ el ] ) ) ;
}
if ( rval ) rval = Format ( " {%s} " , rval ) ; else rval = " {} " ;
return rval ;
}
2014-01-22 19:28:41 +00:00
}
2016-02-07 18:20:50 +00:00
// int as constant? (treat nil as 0 in this case)
if ( constant_prefix )
if ( allow_bitmask )
return GetBitmaskNameByValue ( v , constant_prefix ) ;
else
return GetConstantNameByValueSafe ( v , constant_prefix ) ;
2016-07-13 06:13:23 +00:00
// Strings need to be quoted and escaped
if ( vt = = C4V_String )
{
return Format ( " \" %s \" " , ReplaceString ( ReplaceString ( v , " \\ " , " \\ \\ " ) , " \" " , " \\ \" " ) ) ;
}
2014-01-22 19:28:41 +00:00
// Otherwise, rely on the default %v formatting
2013-12-22 22:47:40 +00:00
return Format ( " %v " , v ) ;
}
global func GetBitmaskNameByValue ( v , prefix )
{
// Compose bitmask of names of individual bits
// e.g. GetBitmaskNameByValue(3, "C4D_") == "C4D_StaticBack|C4D_Structure"
2018-07-24 12:19:26 +00:00
var s ;
2013-12-22 22:47:40 +00:00
for ( var i = 0 ; i < 31 & & v ; + + i )
{
var v2 = 1 < < i ;
if ( v & v2 )
{
v & = ~ v2 ;
var s2 = GetConstantNameByValue ( v2 , prefix ) ;
if ( s2 ) s2 = Format ( " %s%s " , prefix , s2 ) ; else s2 = Format ( " %x " , v2 ) ;
if ( s ) s = Format ( " %s|%s " , s , s2 ) ; else s = s2 ;
}
}
return s ? ? GetConstantNameByValueSafe ( 0 , prefix ) ;
}
global func GetConstantNameByValueSafe ( v , prefix )
{
// Get constant name by value including prefix. If not found, return number as string
var s = GetConstantNameByValue ( v , prefix ) ;
if ( s ) return Format ( " %s%s " , prefix , s ) ; else return Format ( " %d " , v ) ;
}
/* SaveScen_PropList functions */
// I would like to use non-global here, but how can I take a pointer then?
2014-12-12 22:27:17 +00:00
global func SaveScenP_Add ( string name , string s , . . . )
2013-12-22 22:47:40 +00:00
{
// apply format parametrers
s = Format ( s , . . . ) ;
// just append to array of strings
// could build a string using data=Format("%s%s",data,s); here - however, then we'd be limited to some internal buffer sizes
var new_data = { name = name , s = s } ;
if ( ! this . data ) this . data = [ new_data ] ; else this . data [ GetLength ( this . data ) ] = new_data ;
return true ;
}
global func SaveScenP_Remove ( string name )
{
// return all lines identified by name. return number of lines removed
var idx , n = 0 , n_data ;
if ( this . data )
{
n_data = GetLength ( this . data ) ;
while ( true )
{
idx = - 1 ;
while ( + + idx < n_data ) if ( this . data [ idx ] . name = = name ) break ;
if ( idx = = n_data ) break ;
+ + n ;
if ( ! - - n_data ) { this . data = nil ; break ; }
if ( idx < n_data ) this . data [ idx ] = this . data [ n_data ] ;
SetLength ( this . data , n_data ) ;
}
}
return n ;
}
global func SaveScenP_RemoveCreation ( )
{
// Remove any creation strings
2015-01-18 13:27:32 +00:00
return this - > Remove ( SAVEOBJ_ContentsCreation ) + this - > Remove ( SAVEOBJ_ContentsCreationEx ) + this - > Remove ( SAVEOBJ_Creation ) ;
2013-12-22 22:47:40 +00:00
}
global func SaveScenP_Clear ( )
{
// Remove all property and creation data
this . data = nil ;
return true ;
}
2014-12-12 22:27:17 +00:00
global func SaveScenP_AddCall ( string name , proplist obj , string set_fn , . . . )
2013-12-22 22:47:40 +00:00
{
// add string of style Obj123->SetFoo(bar, baz, ...)
// string parameters will not be quoted, so the caller can do some parameter formatting
// compose parameter string
var max_pars = 10 , last_written = 2 , set_pars = " " , n_pars = 0 ;
for ( var i = 3 ; i < max_pars ; + + i )
{
var par = Par ( i ) ;
var par_type = GetType ( par ) ;
if ( par_type )
{
while ( + + last_written < i )
if ( n_pars + + )
set_pars = Format ( " %s%s " , set_pars , " , nil " ) ;
else
set_pars = " nil " ;
if ( par_type ! = C4V_String ) par = SaveScenarioValue2String ( par ) ;
if ( n_pars + + )
set_pars = Format ( " %s, %s " , set_pars , par ) ;
else
set_pars = par ;
}
}
// add setter
return this - > Add ( name , " %s->%s(%s) " , obj - > MakeScenarioSaveName ( ) , set_fn , set_pars ) ;
}
global func SaveScenP_AddSet ( string name , object obj , string local_name , value )
{
// add string of style Obj123->local_name = value
// string parameters will not be quoted, so the caller can do some parameter formatting
if ( GetType ( value ) ! = C4V_String ) value = SaveScenarioValue2String ( value ) ;
return this - > Add ( name , " %s.%s = %s " , obj - > MakeScenarioSaveName ( ) , local_name , value ) ;
}
global func SaveScenP_Buffer2File ( f )
{
// buffer.data is an array of strings to be written to file f
if ( ! this . data ) return false ;
var i = 0 , indent = " " ;
for ( var creation in [ true , false ] )
{
for ( var v in this . data )
{
2015-01-18 13:27:32 +00:00
var v_is_creation = ( ( v . name = = SAVEOBJ_Creation ) | | ( v . name = = SAVEOBJ_ContentsCreation ) | | ( v . name = = SAVEOBJ_ContentsCreationEx ) ) ;
2013-12-22 22:47:40 +00:00
if ( v_is_creation ! = creation ) continue ;
if ( i | | ! creation ) indent = " " ;
FileWrite ( f , Format ( " %s%s; \n " , indent , v . s ) ) ;
+ + i ;
}
}
return i ;
}
global func SaveScenP_HasData ( )
{
// Anything added to data array?
return ! ! this . data ;
}
global func SaveScenP_HasCreation ( )
{
// Functions to test whether any creation data has been added
if ( ! this . data ) return false ;
2015-01-18 13:27:32 +00:00
for ( var v in this . data ) if ( ( v . name = = SAVEOBJ_Creation ) | | ( v . name = = SAVEOBJ_ContentsCreation ) | | ( v . name = = SAVEOBJ_ContentsCreationEx ) ) return true ;
2013-12-22 22:47:40 +00:00
return false ;
}
global func SaveScenP_HasProps ( )
{
// Functions to test whether any property data has been added
if ( ! this . data ) return false ;
2015-01-18 13:27:32 +00:00
for ( var v in this . data ) if ( ( v . name ! = SAVEOBJ_Creation ) & & ( v . name ! = SAVEOBJ_ContentsCreation ) & & ( v . name ! = SAVEOBJ_ContentsCreationEx ) ) return true ;
2013-12-22 22:47:40 +00:00
return false ;
}
global func SaveScenP_HasProp ( string prop )
{
// Test if specific prop is present
2015-01-18 13:27:32 +00:00
if ( ! this . data ) return nil ;
for ( var v in this . data ) if ( v . name = = prop ) return v ;
return nil ;
2013-12-22 22:47:40 +00:00
}
global func SaveScenP_TakeProps ( )
{
// Remove all props from this data and add them to a copy of this
var result = { Prototype = this . Prototype } ;
if ( this . data )
{
var creation = nil , props = nil ;
for ( var v in this . data )
2015-01-18 13:27:32 +00:00
if ( ( v . name ! = SAVEOBJ_Creation ) & & ( v . name ! = SAVEOBJ_ContentsCreation ) & & ( v . name ! = SAVEOBJ_ContentsCreationEx ) )
2013-12-22 22:47:40 +00:00
if ( ! props ) props = [ v ] ; else props [ GetLength ( props ) ] = v ;
else
if ( ! creation ) creation = [ v ] ; else creation [ GetLength ( creation ) ] = v ;
this . data = creation ;
result . data = props ;
result . origin = this ;
}
return result ;
}
2016-08-25 04:18:11 +00:00
global func CustomInitialize ( string script )
{
// run a custom object initialization and
return eval ( this . CustomInitializationScript = script ) ;
}