]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc
Transifex autosync
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / sandbox / sv_sandbox.qc
1 #include "sv_sandbox.qh"
2
3 #include <server/intermission.qh>
4
5 string autocvar_g_sandbox;
6 int autocvar_g_sandbox_info;
7 bool autocvar_g_sandbox_readonly;
8 string autocvar_g_sandbox_storage_name;
9 float autocvar_g_sandbox_storage_autosave;
10 bool autocvar_g_sandbox_storage_autoload;
11 float autocvar_g_sandbox_editor_flood;
12 int autocvar_g_sandbox_editor_maxobjects;
13 int autocvar_g_sandbox_editor_free;
14 float autocvar_g_sandbox_editor_distance_spawn;
15 float autocvar_g_sandbox_editor_distance_edit;
16 float autocvar_g_sandbox_object_scale_min;
17 float autocvar_g_sandbox_object_scale_max;
18 float autocvar_g_sandbox_object_material_velocity_min;
19 float autocvar_g_sandbox_object_material_velocity_factor;
20
21 IntrusiveList g_sandbox_objects;
22 float autosave_time;
23 void sandbox_Database_Load();
24
25 REGISTER_MUTATOR(sandbox, expr_evaluate(autocvar_g_sandbox))
26 {
27         MUTATOR_ONADD
28         {
29                 g_sandbox_objects = IL_NEW();
30                 autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame
31                 if(autocvar_g_sandbox_storage_autoload)
32                         sandbox_Database_Load();
33         }
34 }
35
36 const float MAX_STORAGE_ATTACHMENTS = 16;
37 float object_count;
38 .float object_flood;
39 .entity object_attach;
40 .string material;
41
42 .float touch_timer;
43 void sandbox_ObjectFunction_Touch(entity this, entity toucher)
44 {
45         // apply material impact effects
46
47         if(!this.material)
48                 return;
49         if(this.touch_timer > time)
50                 return; // don't execute each frame
51         this.touch_timer = time + 0.1;
52
53         // make particle count and sound volume depend on impact speed
54         float intensity;
55         intensity = vlen(this.velocity) + vlen(toucher.velocity);
56         if(intensity) // avoid divisions by 0
57                 intensity /= 2; // average the two velocities
58         if (!(intensity >= autocvar_g_sandbox_object_material_velocity_min))
59                 return; // impact not strong enough to do anything
60         // now offset intensity and apply it to the effects
61         intensity -= autocvar_g_sandbox_object_material_velocity_min; // start from minimum velocity, not actual velocity
62         intensity = bound(0, intensity * autocvar_g_sandbox_object_material_velocity_factor, 1);
63
64         _sound(this, CH_TRIGGER, strcat("object/impact_", this.material, "_", ftos(ceil(random() * 5)) , ".wav"), VOL_BASE * intensity, ATTEN_NORM);
65         Send_Effect_(strcat("impact_", this.material), this.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10
66 }
67
68 void sandbox_ObjectFunction_Think(entity this)
69 {
70         // decide if and how this object can be grabbed
71         if(autocvar_g_sandbox_readonly)
72                 this.grab = 0; // no grabbing
73         else if(autocvar_g_sandbox_editor_free < 2 && this.crypto_idfp)
74                 this.grab = 1; // owner only
75         else
76                 this.grab = 3; // anyone
77
78         // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server).
79         // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this,
80         // since if the owning player disconnects, the object's owner should also be reset.
81
82         // bots can't have objects
83         FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
84                 if(this.crypto_idfp == it.crypto_idfp)
85                 {
86                         this.realowner = it;
87                         break;
88                 }
89                 this.realowner = NULL;
90         });
91
92         this.nextthink = time;
93
94         CSQCMODEL_AUTOUPDATE(this);
95 }
96
97 .float old_solid, old_movetype;
98 entity sandbox_ObjectEdit_Get(entity this, float permissions)
99 {
100         // Returns the traced entity if the player can edit it, and NULL if not.
101         // If permissions if false, the object is returned regardless of editing rights.
102         // Attached objects are SOLID_NOT and do not get traced.
103
104         crosshair_trace_plusvisibletriggers(this);
105         if(vdist(this.origin - trace_ent.origin, >, autocvar_g_sandbox_editor_distance_edit))
106                 return NULL; // out of trace range
107         if(trace_ent.classname != "object")
108                 return NULL; // entity is not an object
109         if(!permissions)
110                 return trace_ent; // don't check permissions, anyone can edit this object
111         if(trace_ent.crypto_idfp == "")
112                 return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it
113         if (!(trace_ent.realowner != this && autocvar_g_sandbox_editor_free < 2))
114                 return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server
115         return NULL;
116 }
117
118 void sandbox_ObjectEdit_Scale(entity e, float f)
119 {
120         e.scale = f;
121         if(e.scale)
122         {
123                 e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max);
124                 _setmodel(e, e.model); // reset mins and maxs based on mesh
125                 // apply object scaling and prevent any float precision issues like #2742
126                 setsize(e, RoundPerfectVector(e.mins * e.scale), RoundPerfectVector(e.maxs * e.scale));
127         }
128 }
129
130 void sandbox_ObjectAttach_Remove(entity e);
131 void sandbox_ObjectAttach_Set(entity e, entity parent, string s)
132 {
133         // attaches e to parent on string s
134
135         // we can't attach to an attachment, for obvious reasons
136         sandbox_ObjectAttach_Remove(e);
137
138         e.old_solid = e.solid; // persist solidity
139         e.old_movetype = e.move_movetype; // persist physics
140         set_movetype(e, MOVETYPE_FOLLOW);
141         e.solid = SOLID_NOT;
142         e.takedamage = DAMAGE_NO;
143
144         setattachment(e, parent, s);
145         e.owner = parent;
146 }
147
148 void sandbox_ObjectAttach_Remove(entity e)
149 {
150         // detaches any object attached to e
151
152         IL_EACH(g_sandbox_objects, it.owner == e,
153         {
154                 vector org = gettaginfo(it, 0);
155                 setattachment(it, NULL, "");
156                 it.owner = NULL;
157
158                 // objects change origin and angles when detached, so apply previous position
159                 setorigin(it, org);
160                 it.angles = e.angles; // don't allow detached objects to spin or roll
161
162                 it.solid = it.old_solid; // restore persisted solidity
163                 set_movetype(it, it.old_movetype); // restore persisted physics
164                 it.takedamage = DAMAGE_AIM;
165         });
166 }
167
168 entity sandbox_ObjectSpawn(entity this, float database)
169 {
170         // spawn a new object with default properties
171
172         entity e = new(object);
173         IL_PUSH(g_sandbox_objects, e);
174         e.takedamage = DAMAGE_AIM;
175         e.damageforcescale = 1;
176         e.solid = SOLID_BBOX; // SOLID_BSP would be best, but can lag the server badly
177         set_movetype(e, MOVETYPE_TOSS);
178         e.frame = 0;
179         e.skin = 0;
180         e.material = string_null;
181         settouch(e, sandbox_ObjectFunction_Touch);
182         setthink(e, sandbox_ObjectFunction_Think);
183         e.nextthink = time;
184         //e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects?
185
186         if(!database)
187         {
188                 // set the object's owner via player UID
189                 // if the player does not have an UID, the owner cannot be stored and their objects may be edited by anyone
190                 if(this.crypto_idfp != "")
191                         e.crypto_idfp = strzone(this.crypto_idfp);
192                 else
193                         print_to(this, "^1SANDBOX - WARNING: ^7You spawned an object, but lack a player UID. ^1Your objects are not secured and can be edited by any player!");
194
195                 // set public object information
196                 e.netname = strzone(this.netname); // name of the owner
197                 e.message = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // creation time
198                 e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // last editing time
199
200                 // set origin and direction based on player position and view angle
201                 makevectors(this.v_angle);
202                 WarpZone_TraceLine(this.origin + this.view_ofs, this.origin + this.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_spawn, MOVE_NORMAL, this);
203                 setorigin(e, trace_endpos);
204                 e.angles_y = this.v_angle.y;
205         }
206
207         CSQCMODEL_AUTOINIT(e);
208
209         object_count += 1;
210         return e;
211 }
212
213 void sandbox_ObjectRemove(entity e)
214 {
215         sandbox_ObjectAttach_Remove(e); // detach child objects
216
217         // if the object being removed has been selected for attachment by a player, unset it
218         FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it.object_attach == e, { it.object_attach = NULL; });
219
220         strfree(e.material);
221         strfree(e.crypto_idfp);
222         strfree(e.netname);
223         strfree(e.message);
224         strfree(e.message2);
225         delete(e);
226         e = NULL;
227
228         object_count -= 1;
229 }
230
231 string port_string[MAX_STORAGE_ATTACHMENTS]; // fteqcc crashes if this isn't defined as a global
232
233 string sandbox_ObjectPort_Save(entity e, bool database)
234 {
235         // save object properties, and return them as a string
236         int o = 0;
237
238         // order doesn't really matter, as we're writing the file fresh
239         IL_EACH(g_sandbox_objects, it == e || it.owner == e, LAMBDA(
240                 // the main object needs to be first in the array [0] with attached objects following
241                 int slot, physics, solidity;
242                 if(it == e) // this is the main object, place it first
243                 {
244                         slot = 0;
245                         solidity = it.solid; // applied solidity is normal solidity for children
246                         physics = it.move_movetype; // applied physics are normal physics for parents
247                 }
248                 else if(it.owner == e) // child object, list them in order
249                 {
250                         o += 1; // children start from 1
251                         slot = o;
252                         solidity = it.old_solid; // persisted solidity is normal solidity for children
253                         physics = it.old_movetype; // persisted physics are normal physics for children
254                         gettaginfo(it.owner, it.tag_index); // get the name of the tag our object is attached to, used further below
255                 }
256                 else
257                         continue;
258
259                 // ---------------- OBJECT PROPERTY STORAGE: SAVE ----------------
260                 if(slot)
261                 {
262                         // properties stored only for child objects
263                         if(gettaginfo_name)
264                                 port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" ");
265                         else
266                                 port_string[slot] = strcat(port_string[slot], "\"\" "); // none
267                 }
268                 else
269                 {
270                         // properties stored only for parent objects
271                         if(database)
272                         {
273                                 port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", it.origin), " ");
274                                 port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", it.angles), " ");
275                         }
276                 }
277                 // properties stored for all objects
278                 port_string[slot] = strcat(port_string[slot], "\"", it.model, "\" ");
279                 port_string[slot] = strcat(port_string[slot], ftos(it.skin), " ");
280                 port_string[slot] = strcat(port_string[slot], ftos(it.alpha), " ");
281                 port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", it.colormod), " ");
282                 port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", it.glowmod), " ");
283                 port_string[slot] = strcat(port_string[slot], ftos(it.frame), " ");
284                 port_string[slot] = strcat(port_string[slot], ftos(it.scale), " ");
285                 port_string[slot] = strcat(port_string[slot], ftos(solidity), " ");
286                 port_string[slot] = strcat(port_string[slot], ftos(physics), " ");
287                 port_string[slot] = strcat(port_string[slot], ftos(it.damageforcescale), " ");
288                 if(it.material)
289                         port_string[slot] = strcat(port_string[slot], "\"", it.material, "\" ");
290                 else
291                         port_string[slot] = strcat(port_string[slot], "\"\" "); // none
292                 if(database)
293                 {
294                         // properties stored only for the database
295                         if(it.crypto_idfp)
296                                 port_string[slot] = strcat(port_string[slot], "\"", it.crypto_idfp, "\" ");
297                         else
298                                 port_string[slot] = strcat(port_string[slot], "\"\" "); // none
299                         port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" ");
300                         port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" ");
301                         port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" ");
302                 }
303         ));
304
305         // now apply the array to a simple string, with the ; symbol separating objects
306         string s = "";
307         for(int j = 0; j <= MAX_STORAGE_ATTACHMENTS; ++j)
308         {
309                 if(port_string[j])
310                         s = strcat(s, port_string[j], "; ");
311                 port_string[j] = string_null; // fully clear the string
312         }
313
314         return s;
315 }
316
317 entity sandbox_ObjectPort_Load(entity this, string s, float database)
318 {
319         // load object properties, and spawn a new object with them
320         int n, i;
321         entity e = NULL, parent = NULL;
322         string arg = string_null;
323
324         // separate objects between the ; symbols
325         n = tokenizebyseparator(s, "; ");
326         for(i = 0; i < n; ++i)
327                 port_string[i] = argv(i);
328
329         // now separate and apply the properties of each object
330         for(i = 0; i < n; ++i)
331         {
332                 #define SANDBOX_GETARG arg = argv(++argv_num);
333                 int argv_num = -1; // starts at -1 so I don't need postincrement
334
335                 string tagname = string_null;
336                 tokenize_console(port_string[i]);
337                 e = sandbox_ObjectSpawn(this, database);
338
339                 // ---------------- OBJECT PROPERTY STORAGE: LOAD ----------------
340                 if(i)
341                 {
342                         // properties stored only for child objects
343                         SANDBOX_GETARG; tagname = (arg != "") ? arg : string_null;
344                 }
345                 else
346                 {
347                         // properties stored only for parent objects
348                         if(database)
349                         {
350                                 SANDBOX_GETARG; setorigin(e, stov(arg));
351                                 SANDBOX_GETARG; e.angles = stov(arg);
352                         }
353                         parent = e; // mark parent objects as such
354                 }
355                 // properties stored for all objects
356                 SANDBOX_GETARG; _setmodel(e, arg);
357                 SANDBOX_GETARG; e.skin = stof(arg);
358                 SANDBOX_GETARG; e.alpha = stof(arg);
359                 SANDBOX_GETARG; e.colormod = stov(arg);
360                 SANDBOX_GETARG; e.glowmod = stov(arg);
361                 SANDBOX_GETARG; e.frame = stof(arg);
362                 SANDBOX_GETARG; sandbox_ObjectEdit_Scale(e, stof(arg));
363                 SANDBOX_GETARG; e.solid = e.old_solid = stof(arg);
364                 SANDBOX_GETARG; e.old_movetype = stof(arg);
365                 set_movetype(e, e.old_movetype);
366                 SANDBOX_GETARG; e.damageforcescale = stof(arg);
367                 strfree(e.material);
368                 SANDBOX_GETARG; e.material = (arg != "") ? strzone(arg) : string_null;
369                 if(database)
370                 {
371                         // properties stored only for the database
372                         strfree(e.crypto_idfp);
373                         SANDBOX_GETARG; e.crypto_idfp = (arg != "") ? strzone(arg) : string_null;
374                         SANDBOX_GETARG; strcpy(e.netname, arg);
375                         SANDBOX_GETARG; strcpy(e.message, arg);
376                         SANDBOX_GETARG; strcpy(e.message2, arg);
377                 }
378
379                 // attach last
380                 if(i)
381                         sandbox_ObjectAttach_Set(e, parent, tagname);
382         }
383
384         for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i)
385                 port_string[i] = string_null; // fully clear the string
386
387         return e;
388 }
389
390 void sandbox_Database_Save()
391 {
392         // saves all objects to the database file
393         string file_name;
394         float file_get;
395
396         file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt");
397         file_get = fopen(file_name, FILE_WRITE);
398         fputs(file_get, strcat("// sandbox storage \"", autocvar_g_sandbox_storage_name, "\" for map \"", GetMapname(), "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S")));
399         fputs(file_get, strcat(" containing ", ftos(object_count), " objects\n"));
400
401         IL_EACH(g_sandbox_objects, !it.owner, // attached objects are persisted separately, ignore them here
402         {
403                 // use a line of text for each object, listing all properties
404                 fputs(file_get, strcat(sandbox_ObjectPort_Save(it, true), "\n"));
405         });
406         fclose(file_get);
407 }
408
409 void sandbox_Database_Load()
410 {
411         // loads all objects from the database file
412         string file_read, file_name;
413         float file_get, i;
414
415         file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt");
416         file_get = fopen(file_name, FILE_READ);
417         if(file_get < 0)
418         {
419                 if(autocvar_g_sandbox_info > 0)
420                         LOG_INFO("^3SANDBOX - SERVER: ^7could not find storage file ^3", file_name, "^7, no objects were loaded");
421         }
422         else
423         {
424                 for (;;)
425                 {
426                         file_read = fgets(file_get);
427                         if(file_read == "")
428                                 break;
429                         if(substring(file_read, 0, 2) == "//")
430                                 continue;
431                         if(substring(file_read, 0, 1) == "#")
432                                 continue;
433
434                         entity e;
435                         e = sandbox_ObjectPort_Load(NULL, file_read, true);
436
437                         if(e.material)
438                         {
439                                 // since objects are being loaded for the first time, precache material sounds for each
440                                 for (i = 1; i <= 5; ++i) // 5 sounds in total
441                                         precache_sound(strcat("object/impact_", e.material, "_", ftos(i), ".wav"));
442                         }
443                 }
444                 if(autocvar_g_sandbox_info > 0)
445                         LOG_INFO("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name);
446         }
447         fclose(file_get);
448 }
449
450 MUTATOR_HOOKFUNCTION(sandbox, SV_ParseClientCommand)
451 {
452         if(MUTATOR_RETURNVALUE) // command was already handled?
453                 return;
454
455         entity player = M_ARGV(0, entity);
456         string cmd_name = M_ARGV(1, string);
457         int cmd_argc = M_ARGV(2, int);
458
459         if(cmd_name == "g_sandbox")
460         {
461                 if(autocvar_g_sandbox_readonly)
462                 {
463                         print_to(player, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used");
464                         return true;
465                 }
466                 if(cmd_argc < 2)
467                 {
468                         print_to(player, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'");
469                         return true;
470                 }
471
472                 switch(argv(1))
473                 {
474                         entity e;
475                         int j;
476                         string s;
477
478                         // ---------------- COMMAND: HELP ----------------
479                         case "help":
480                                 print_to(player, "You can use the following sandbox commands:");
481                                 print_to(player, "^7\"^2object_spawn ^3models/foo/bar.md3^7\" spawns a new object in front of the player, and gives it the specified model");
482                                 print_to(player, "^7\"^2object_remove^7\" removes the object the player is looking at. Players can only remove their own objects");
483                                 print_to(player, "^7\"^2object_duplicate ^3value^7\" duplicates the object, if the player has copying rights over the original");
484                                 print_to(player, "^3copy value ^7- copies the properties of the object to the specified client cvar");
485                                 print_to(player, "^3paste value ^7- spawns an object with the given properties. Properties or cvars must be specified as follows; eg1: \"0 1 2 ...\", eg2: \"$cl_cvar\"");
486                                 print_to(player, "^7\"^2object_attach ^3property value^7\" attaches one object to another. Players can only attach their own objects");
487                                 print_to(player, "^3get ^7- selects the object you are facing as the object to be attached");
488                                 print_to(player, "^3set value ^7- attaches the previously selected object to the object you are facing, on the specified bone");
489                                 print_to(player, "^3remove ^7- detaches all objects from the object you are facing");
490                                 print_to(player, "^7\"^2object_edit ^3property value^7\" edits the given property of the object. Players can only edit their own objects");
491                                 print_to(player, "^3skin value ^7- changes the skin of the object");
492                                 print_to(player, "^3alpha value ^7- sets object transparency");
493                                 print_to(player, "^3colormod \"value_x value_y value_z\" ^7- main object color");
494                                 print_to(player, "^3glowmod \"value_x value_y value_z\" ^7- glow object color");
495                                 print_to(player, "^3frame value ^7- object animation frame, for self-animated models");
496                                 print_to(player, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size");
497                                 print_to(player, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid");
498                                 print_to(player, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical");
499                                 print_to(player, "^3force value ^7- amount of force applied to objects that are shot");
500                                 print_to(player, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh");
501                                 print_to(player, "^7\"^2object_claim^7\" sets the player as the owner of the object, if they have the right to edit it");
502                                 print_to(player, "^7\"^2object_info ^3value^7\" shows public information about the object");
503                                 print_to(player, "^3object ^7- prints general information about the object, such as owner and creation / editing date");
504                                 print_to(player, "^3mesh ^7- prints information about the object's mesh, including skeletal bones");
505                                 print_to(player, "^3attachments ^7- prints information about the object's attachments");
506                                 print_to(player, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects");
507                                 return true;
508
509                         // ---------------- COMMAND: OBJECT, SPAWN ----------------
510                         case "object_spawn":
511                                 if(time < player.object_flood)
512                                 {
513                                         print_to(player, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(player.object_flood - time), " ^7seconds beofore spawning another object"));
514                                         return true;
515                                 }
516                                 player.object_flood = time + autocvar_g_sandbox_editor_flood;
517                                 if(object_count >= autocvar_g_sandbox_editor_maxobjects)
518                                 {
519                                         print_to(player, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time"));
520                                         return true;
521                                 }
522                                 if(cmd_argc < 3)
523                                 {
524                                         print_to(player, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command");
525                                         return true;
526                                 }
527                                 if (!(fexists(argv(2))))
528                                 {
529                                         print_to(player, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct");
530                                         return true;
531                                 }
532
533                                 e = sandbox_ObjectSpawn(player, false);
534                                 _setmodel(e, argv(2));
535
536                                 if(autocvar_g_sandbox_info > 0)
537                                         LOG_INFO("^3SANDBOX - SERVER: ^7", player.netname, " spawned an object at origin ^3", vtos(e.origin));
538                                 return true;
539
540                         // ---------------- COMMAND: OBJECT, REMOVE ----------------
541                         case "object_remove":
542                                 e = sandbox_ObjectEdit_Get(player, true);
543                                 if(e != NULL)
544                                 {
545                                         if(autocvar_g_sandbox_info > 0)
546                                                 LOG_INFO("^3SANDBOX - SERVER: ^7", player.netname, " removed an object at origin ^3", vtos(e.origin));
547                                         sandbox_ObjectRemove(e);
548                                         return true;
549                                 }
550
551                                 print_to(player, "^1SANDBOX - WARNING: ^7Object could not be removed. Make sure you are facing an object that you have edit rights over");
552                                 return true;
553
554                         // ---------------- COMMAND: OBJECT, DUPLICATE ----------------
555                         case "object_duplicate":
556                                 switch(argv(2))
557                                 {
558                                         case "copy":
559                                                 // copies customizable properties of the selected object to the clipboard cvar
560                                                 e = sandbox_ObjectEdit_Get(player, autocvar_g_sandbox_editor_free); // can we copy objects we can't edit?
561                                                 if(e != NULL)
562                                                 {
563                                                         s = sandbox_ObjectPort_Save(e, false);
564                                                         s = strreplace("\"", "\\\"", s);
565                                                         stuffcmd(player, strcat("set ", argv(3), " \"", s, "\""));
566
567                                                         print_to(player, "^2SANDBOX - INFO: ^7Object copied to clipboard");
568                                                         return true;
569                                                 }
570                                                 print_to(player, "^1SANDBOX - WARNING: ^7Object could not be copied. Make sure you are facing an object that you have copy rights over");
571                                                 return true;
572
573                                         case "paste":
574                                                 // spawns a new object using the properties in the player's clipboard cvar
575                                                 if(time < player.object_flood)
576                                                 {
577                                                         print_to(player, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(player.object_flood - time), " ^7seconds beofore spawning another object"));
578                                                         return true;
579                                                 }
580                                                 player.object_flood = time + autocvar_g_sandbox_editor_flood;
581                                                 if(argv(3) == "") // no object in clipboard
582                                                 {
583                                                         print_to(player, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it");
584                                                         return true;
585                                                 }
586                                                 if(object_count >= autocvar_g_sandbox_editor_maxobjects)
587                                                 {
588                                                         print_to(player, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time"));
589                                                         return true;
590                                                 }
591                                                 e = sandbox_ObjectPort_Load(player, argv(3), false);
592
593                                                 print_to(player, "^2SANDBOX - INFO: ^7Object pasted successfully");
594                                                 if(autocvar_g_sandbox_info > 0)
595                                                         LOG_INFO("^3SANDBOX - SERVER: ^7", player.netname, " pasted an object at origin ^3", vtos(e.origin));
596                                                 return true;
597                                 }
598                                 return true;
599
600                         // ---------------- COMMAND: OBJECT, ATTACH ----------------
601                         case "object_attach":
602                                 switch(argv(2))
603                                 {
604                                         case "get":
605                                                 // select e as the object as meant to be attached
606                                                 e = sandbox_ObjectEdit_Get(player, true);
607                                                 if(e != NULL)
608                                                 {
609                                                         player.object_attach = e;
610                                                         print_to(player, "^2SANDBOX - INFO: ^7Object selected for attachment");
611                                                         return true;
612                                                 }
613                                                 print_to(player, "^1SANDBOX - WARNING: ^7Object could not be selected for attachment. Make sure you are facing an object that you have edit rights over");
614                                                 return true;
615                                         case "set":
616                                                 if(player.object_attach == NULL)
617                                                 {
618                                                         print_to(player, "^1SANDBOX - WARNING: ^7No object selected for attachment. Please select an object to be attached first.");
619                                                         return true;
620                                                 }
621
622                                                 // attaches the previously selected object to e
623                                                 e = sandbox_ObjectEdit_Get(player, true);
624                                                 if(e != NULL)
625                                                 {
626                                                         sandbox_ObjectAttach_Set(player.object_attach, e, argv(3));
627                                                         player.object_attach = NULL; // object was attached, no longer keep it scheduled for attachment
628                                                         print_to(player, "^2SANDBOX - INFO: ^7Object attached successfully");
629                                                         if(autocvar_g_sandbox_info > 1)
630                                                                 LOG_INFO("^3SANDBOX - SERVER: ^7", player.netname, " attached objects at origin ^3", vtos(e.origin));
631                                                         return true;
632                                                 }
633                                                 print_to(player, "^1SANDBOX - WARNING: ^7Object could not be attached to the parent. Make sure you are facing an object that you have edit rights over");
634                                                 return true;
635                                         case "remove":
636                                                 // removes e if it was attached
637                                                 e = sandbox_ObjectEdit_Get(player, true);
638                                                 if(e != NULL)
639                                                 {
640                                                         sandbox_ObjectAttach_Remove(e);
641                                                         print_to(player, "^2SANDBOX - INFO: ^7Child objects detached successfully");
642                                                         if(autocvar_g_sandbox_info > 1)
643                                                                 LOG_INFO("^3SANDBOX - SERVER: ^7", player.netname, " detached objects at origin ^3", vtos(e.origin));
644                                                         return true;
645                                                 }
646                                                 print_to(player, "^1SANDBOX - WARNING: ^7Child objects could not be detached. Make sure you are facing an object that you have edit rights over");
647                                                 return true;
648                                 }
649                                 return true;
650
651                         // ---------------- COMMAND: OBJECT, EDIT ----------------
652                         case "object_edit":
653                                 if(argv(2) == "")
654                                 {
655                                         print_to(player, "^1SANDBOX - WARNING: ^7Too few parameters. You must specify a property to edit");
656                                         return true;
657                                 }
658
659                                 e = sandbox_ObjectEdit_Get(player, true);
660                                 if(e != NULL)
661                                 {
662                                         switch(argv(2))
663                                         {
664                                                 case "skin":
665                                                         e.skin = stof(argv(3));
666                                                         break;
667                                                 case "alpha":
668                                                         e.alpha = stof(argv(3));
669                                                         break;
670                                                 case "color_main":
671                                                         e.colormod = stov(argv(3));
672                                                         break;
673                                                 case "color_glow":
674                                                         e.glowmod = stov(argv(3));
675                                                         break;
676                                                 case "frame":
677                                                         e.frame = stof(argv(3));
678                                                         break;
679                                                 case "scale":
680                                                         sandbox_ObjectEdit_Scale(e, stof(argv(3)));
681                                                         break;
682                                                 case "solidity":
683                                                         switch(argv(3))
684                                                         {
685                                                                 case "0": // non-solid
686                                                                         e.solid = SOLID_TRIGGER;
687                                                                         break;
688                                                                 case "1": // solid
689                                                                         e.solid = SOLID_BBOX;
690                                                                         break;
691                                                                 default:
692                                                                         break;
693                                                         }
694                                                 case "physics":
695                                                         switch(argv(3))
696                                                         {
697                                                                 case "0": // static
698                                                                         set_movetype(e, MOVETYPE_NONE);
699                                                                         break;
700                                                                 case "1": // movable
701                                                                         set_movetype(e, MOVETYPE_TOSS);
702                                                                         break;
703                                                                 case "2": // physical
704                                                                         set_movetype(e, MOVETYPE_PHYSICS);
705                                                                         break;
706                                                                 default:
707                                                                         break;
708                                                         }
709                                                         break;
710                                                 case "force":
711                                                         e.damageforcescale = stof(argv(3));
712                                                         break;
713                                                 case "material":
714                                                         strfree(e.material);
715                                                         if(argv(3))
716                                                         {
717                                                                 for (j = 1; j <= 5; ++j) // precache material sounds, 5 in total
718                                                                         precache_sound(strcat("object/impact_", argv(3), "_", ftos(j), ".wav"));
719                                                                 e.material = strzone(argv(3));
720                                                         }
721                                                         else
722                                                                 e.material = string_null; // no material
723                                                         break;
724                                                 default:
725                                                         print_to(player, "^1SANDBOX - WARNING: ^7Invalid object property. For usage information, type 'sandbox help'");
726                                                         return true;
727                                         }
728
729                                         // update last editing time
730                                         strcpy(e.message2, strftime(true, "%d-%m-%Y %H:%M:%S"));
731
732                                         if(autocvar_g_sandbox_info > 1)
733                                                 LOG_INFO("^3SANDBOX - SERVER: ^7", player.netname, " edited property ^3", argv(2), " ^7of an object at origin ^3", vtos(e.origin));
734                                         return true;
735                                 }
736
737                                 print_to(player, "^1SANDBOX - WARNING: ^7Object could not be edited. Make sure you are facing an object that you have edit rights over");
738                                 return true;
739
740                         // ---------------- COMMAND: OBJECT, CLAIM ----------------
741                         case "object_claim":
742                                 // if the player can edit an object but is not its owner, this can be used to claim that object
743                                 if(player.crypto_idfp == "")
744                                 {
745                                         print_to(player, "^1SANDBOX - WARNING: ^7You do not have a player UID, and cannot claim objects");
746                                         return true;
747                                 }
748                                 e = sandbox_ObjectEdit_Get(player, true);
749                                 if(e != NULL)
750                                 {
751                                         // update the owner's name
752                                         // Do this before checking if you're already the owner and skipping if such, so we
753                                         // also update the player's nickname if they changed it (but has the same player UID)
754                                         if(e.netname != player.netname)
755                                         {
756                                                 strcpy(e.netname, player.netname);
757                                                 print_to(player, "^2SANDBOX - INFO: ^7Object owner name updated");
758                                         }
759
760                                         if(e.crypto_idfp == player.crypto_idfp)
761                                         {
762                                                 print_to(player, "^2SANDBOX - INFO: ^7Object is already yours, nothing to claim");
763                                                 return true;
764                                         }
765
766                                         strcpy(e.crypto_idfp, player.crypto_idfp);
767
768                                         print_to(player, "^2SANDBOX - INFO: ^7Object claimed successfully");
769                                 }
770                                 print_to(player, "^1SANDBOX - WARNING: ^7Object could not be claimed. Make sure you are facing an object that you have edit rights over");
771                                 return true;
772
773                         // ---------------- COMMAND: OBJECT, INFO ----------------
774                         case "object_info":
775                                 // prints public information about the object to the player
776                                 e = sandbox_ObjectEdit_Get(player, false);
777                                 if(e != NULL)
778                                 {
779                                         switch(argv(2))
780                                         {
781                                                 case "object":
782                                                         print_to(player, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\""));
783                                                         return true;
784                                                 case "mesh":
785                                                         s = "";
786                                                         FOR_EACH_TAG(e)
787                                                                 s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", ");
788                                                         print_to(player, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s));
789                                                         return true;
790                                                 case "attachments":
791                                                         // this should show the same info as 'mesh' but for attachments
792                                                         s = "";
793                                                         j = 0;
794                                                         IL_EACH(g_sandbox_objects, it.owner == e,
795                                                         {
796                                                                 ++j; // start from 1
797                                                                 gettaginfo(e, it.tag_index);
798                                                                 s = strcat(s, "^1attachment ", ftos(j), "^7 has mesh \"^3", it.model, "^7\" at animation frame ^3", ftos(it.frame));
799                                                                 s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", ");
800                                                         });
801                                                         if(j) // object contains attachments
802                                                                 print_to(player, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(j), "^7 attachment(s): ", s));
803                                                         else
804                                                                 print_to(player, "^2SANDBOX - INFO: ^7Object contains no attachments");
805                                                         return true;
806                                         }
807                                 }
808                                 print_to(player, "^1SANDBOX - WARNING: ^7No information could be found. Make sure you are facing an object");
809                                 return true;
810
811                         // ---------------- COMMAND: DEFAULT ----------------
812                         default:
813                                 print_to(player, "Invalid command. For usage information, type 'sandbox help'");
814                                 return true;
815                 }
816         }
817 }
818
819 MUTATOR_HOOKFUNCTION(sandbox, SV_StartFrame)
820 {
821         if(!autocvar_g_sandbox_storage_autosave)
822                 return;
823         if(time < autosave_time)
824                 return;
825         autosave_time = time + autocvar_g_sandbox_storage_autosave;
826
827         sandbox_Database_Save();
828
829         return true;
830 }