const float MAX_STORAGE_ATTACHMENTS = 16;
float object_count;
+.float object_flood;
.entity object_attach;
.string material;
pointparticles(particleeffectnum(strcat("impact_", self.material)), self.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10
}
-entity sandbox_ObjectEdit_Get(float permissions)
+void sandbox_ObjectFunction_Think()
{
- // returns the traced entity if the player can edit it, and world if not
- // if permissions if FALSE, the object is returned regardless of editing rights
- // attached objects are SOLID_NOT and don't risk getting traced
+ entity e;
+
+ // decide if and how this object can be grabbed
+ if(autocvar_g_sandbox_readonly)
+ self.grab = 0; // no grabbing
+ else if(autocvar_g_sandbox_editor_free < 2 && self.crypto_idfp)
+ self.grab = 1; // owner only
+ else
+ self.grab = 3; // anyone
+
+ // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server).
+ // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this,
+ // since if the owning player disconnects, the object's owner should also be reset.
+ FOR_EACH_REALPLAYER(e) // bots can't have objects
+ {
+ if(self.crypto_idfp == e.crypto_idfp)
+ {
+ self.realowner = e;
+ break;
+ }
+ self.realowner = world;
+ }
- makevectors(self.v_angle);
- WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_edit, MOVE_NORMAL, self);
+ self.nextthink = time;
+}
+.float old_solid, old_movetype;
+entity sandbox_ObjectEdit_Get(float permissions)
+{
+ // Returns the traced entity if the player can edit it, and world if not.
+ // If permissions if FALSE, the object is returned regardless of editing rights.
+ // Attached objects are SOLID_NOT and do not get traced.
+
+ crosshair_trace_plusvisibletriggers(self);
+ if(vlen(self.origin - trace_ent.origin) > autocvar_g_sandbox_editor_distance_edit)
+ return world; // out of trace range
if(trace_ent.classname != "object")
return world; // entity is not an object
if(!permissions)
return trace_ent; // don't check permissions, anyone can edit this object
if(!trace_ent.crypto_idfp)
return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it
- if not(trace_ent.crypto_idfp != self.crypto_idfp && autocvar_g_sandbox_editor_free < 2)
+ if not(trace_ent.realowner != self && autocvar_g_sandbox_editor_free < 2)
return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server
return world;
}
if(e.scale)
{
e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max);
+ setmodel(e, e.model); // reset mins and maxs based on mesh
setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size
}
}
-.float old_movetype;
void sandbox_ObjectAttach_Remove(entity e);
void sandbox_ObjectAttach_Set(entity e, entity parent, string s)
{
// we can't attach to an attachment, for obvious reasons
sandbox_ObjectAttach_Remove(e);
+ e.old_solid = e.solid; // persist solidity
e.old_movetype = e.movetype; // persist physics
e.movetype = MOVETYPE_FOLLOW;
e.solid = SOLID_NOT;
if(head.owner == e)
{
vector org;
- head.movetype = head.old_movetype; // restore persisted physics
- head.solid = SOLID_BBOX;
- head.takedamage = DAMAGE_AIM;
-
org = gettaginfo(head, 0);
setattachment(head, world, "");
head.owner = world;
// objects change origin and angles when detached, so apply previous position
setorigin(head, org);
head.angles = e.angles; // don't allow detached objects to spin or roll
+
+ head.solid = head.old_solid; // restore persisted solidity
+ head.movetype = head.old_movetype; // restore persisted physics
+ head.takedamage = DAMAGE_AIM;
}
}
}
e.skin = 0;
e.material = string_null;
e.touch = sandbox_ObjectFunction_Touch;
+ e.think = sandbox_ObjectFunction_Think;
+ e.nextthink = time;
//e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects?
if(!database)
{
sandbox_ObjectAttach_Remove(e); // detach child objects
+ // if the object being removed has been selected for attachment by a player, unset it
+ entity head;
+ FOR_EACH_REALPLAYER(head) // bots can't have objects
+ {
+ if(head.object_attach == e)
+ head.object_attach = world;
+ }
+
if(e.material) { strunzone(e.material); e.material = string_null; }
if(e.crypto_idfp) { strunzone(e.crypto_idfp); e.crypto_idfp = string_null; }
if(e.netname) { strunzone(e.netname); e.netname = string_null; }
string sandbox_ObjectPort_Save(entity e, float database)
{
// save object properties, and return them as a string
- float i;
+ float i = 0;
string s;
entity head;
for(head = world; (head = find(head, classname, "object")); )
{
// the main object needs to be first in the array [0] with attached objects following
- float slot, physics;
+ float slot, physics, solidity;
if(head == e) // this is the main object, place it first
{
slot = 0;
+ solidity = head.solid; // applied solidity is normal solidity for children
physics = head.movetype; // applied physics are normal physics for parents
}
else if(head.owner == e) // child object, list them in order
{
i += 1; // children start from 1
slot = i;
+ solidity = head.old_solid; // persisted solidity is normal solidity for children
physics = head.old_movetype; // persisted physics are normal physics for children
gettaginfo(head.owner, head.tag_index); // get the name of the tag our object is attached to, used further below
}
if(slot)
{
// properties stored only for child objects
- if(gettaginfo_name) port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" "); else port_string[slot] = strcat(port_string[slot], "- "); // none
+ if(gettaginfo_name) port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none
}
else
{
port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.glowmod), " ");
port_string[slot] = strcat(port_string[slot], ftos(head.frame), " ");
port_string[slot] = strcat(port_string[slot], ftos(head.scale), " ");
+ port_string[slot] = strcat(port_string[slot], ftos(solidity), " ");
port_string[slot] = strcat(port_string[slot], ftos(physics), " ");
port_string[slot] = strcat(port_string[slot], ftos(head.damageforcescale), " ");
- if(head.material) port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" "); else port_string[slot] = strcat(port_string[slot], "- "); // none
+ if(head.material) port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none
if(database)
{
// properties stored only for the database
- if(head.crypto_idfp) port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" "); else port_string[slot] = strcat(port_string[slot], "- "); // none
+ if(head.crypto_idfp) port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none
port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" ");
port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" ");
port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" ");
}
// now apply the array to a simple string, with the ; symbol separating objects
+ s = "";
for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i)
{
if(port_string[i])
{
// load object properties, and spawn a new object with them
float n, i;
- entity e, parent;
+ entity e = world, parent = world;
// separate objects between the ; symbols
n = tokenizebyseparator(s, "; ");
for(i = 0; i < n; ++i)
{
float argv_num;
- string tagname;
+ string tagname = string_null;
argv_num = 0;
tokenize_console(port_string[i]);
e = sandbox_ObjectSpawn(database);
if(i)
{
// properties stored only for child objects
- if(argv(argv_num) != "-") tagname = argv(argv_num); else tagname = string_null; ++argv_num;
+ if(argv(argv_num) != "") tagname = argv(argv_num); else tagname = string_null; ++argv_num;
}
else
{
e.glowmod = stov(argv(argv_num)); ++argv_num;
e.frame = stof(argv(argv_num)); ++argv_num;
sandbox_ObjectEdit_Scale(e, stof(argv(argv_num))); ++argv_num;
+ e.solid = e.old_solid = stof(argv(argv_num)); ++argv_num;
e.movetype = e.old_movetype = stof(argv(argv_num)); ++argv_num;
e.damageforcescale = stof(argv(argv_num)); ++argv_num;
- if(e.material) strunzone(e.material); if(argv(argv_num) != "-") e.material = strzone(argv(argv_num)); else e.material = string_null; ++argv_num;
+ if(e.material) strunzone(e.material); if(argv(argv_num) != "") e.material = strzone(argv(argv_num)); else e.material = string_null; ++argv_num;
if(database)
{
// properties stored only for the database
- if(e.crypto_idfp) strunzone(e.crypto_idfp); if(argv(argv_num) != "-") e.crypto_idfp = strzone(argv(argv_num)); else e.crypto_idfp = string_null; ++argv_num;
+ if(e.crypto_idfp) strunzone(e.crypto_idfp); if(argv(argv_num) != "") e.crypto_idfp = strzone(argv(argv_num)); else e.crypto_idfp = string_null; ++argv_num;
if(e.netname) strunzone(e.netname); e.netname = strzone(argv(argv_num)); ++argv_num;
if(e.message) strunzone(e.message); e.message = strzone(argv(argv_num)); ++argv_num;
if(e.message2) strunzone(e.message2); e.message2 = strzone(argv(argv_num)); ++argv_num;
if(autocvar_g_sandbox_info > 0)
print(strcat("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name, "\n"));
}
+ fclose(file_get);
}
MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
return FALSE;
if(cmd_name == "g_sandbox")
{
+ if(autocvar_g_sandbox_readonly)
+ {
+ print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used");
+ return TRUE;
+ }
if(cmd_argc < 2)
{
- print_to(self, "Sandbox mode is active. For usage information, type 'sandbox help'");
+ print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'");
return TRUE;
}
print_to(self, "^3glowmod \"value_x value_y value_z\" ^7- glow object color");
print_to(self, "^3frame value ^7- object animation frame, for self-animated models");
print_to(self, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size");
+ print_to(self, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid");
print_to(self, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical");
print_to(self, "^3force value ^7- amount of force applied to objects that are shot");
print_to(self, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh");
// ---------------- COMMAND: OBJECT, SPAWN ----------------
case "object_spawn":
+ if(time < self.object_flood)
+ {
+ print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object"));
+ return TRUE;
+ }
+ self.object_flood = time + autocvar_g_sandbox_editor_flood;
if(object_count >= autocvar_g_sandbox_editor_maxobjects)
{
print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time"));
print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command");
return TRUE;
}
- else if not(fexists(argv(2)))
+ if not(fexists(argv(2)))
{
print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct");
return TRUE;
case "paste":
// spawns a new object using the properties in the player's clipboard cvar
+ if(time < self.object_flood)
+ {
+ print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object"));
+ return TRUE;
+ }
+ self.object_flood = time + autocvar_g_sandbox_editor_flood;
if(!argv(3)) // no object in clipboard
{
print_to(self, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it");
case "scale":
sandbox_ObjectEdit_Scale(e, stof(argv(3)));
break;
+ case "solidity":
+ switch(argv(3))
+ {
+ case "0": // non-solid
+ e.solid = SOLID_TRIGGER;
+ break;
+ case "1": // solid
+ e.solid = SOLID_BBOX;
+ break;
+ default:
+ break;
+ }
case "physics":
switch(argv(3))
{
print_to(self, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\""));
return TRUE;
case "mesh":
- for(i = 1; gettaginfo(e, i); i++)
+ s = "";
+ FOR_EACH_TAG(e)
s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", ");
print_to(self, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s));
return TRUE;
case "attachments":
// this should show the same info as 'mesh' but for attachments
+ s = "";
entity head;
+ i = 0;
for(head = world; (head = find(head, classname, "object")); )
{
if(head.owner == e)
return FALSE;
}
-MUTATOR_HOOKFUNCTION(sandbox_PlayerPreThink)
-{
- // if the player is close enough to their object, they can drag it
-
- if(autocvar_sv_cheats)
- return FALSE; // cheat dragging is used instead
-
- // grab is TRUE if the object can be picked up. While an object is being carried, the Drag() function
- // must execute for it either way, otherwise it would cause bugs if it went out of the player's trace.
- // This also makes sure that an object can only pe picked up if in range, but does not get dropped if
- // it goes out of range while slinging it around.
-
- entity e;
- float grab;
-
- e = sandbox_ObjectEdit_Get(TRUE);
- if(e != world && vlen(e.origin - self.origin) <= autocvar_g_sandbox_editor_distance_edit)
- grab = TRUE;
-
- if(Drag(e, grab)) // execute dragging
- return TRUE;
-
- return FALSE;
-}
-
float autosave_time;
MUTATOR_HOOKFUNCTION(sandbox_StartFrame)
{
return TRUE;
}
+MUTATOR_HOOKFUNCTION(sandbox_SetModname)
+{
+ modname = "Sandbox";
+ return TRUE;
+}
+
MUTATOR_DEFINITION(sandbox)
{
MUTATOR_HOOK(SV_ParseClientCommand, sandbox_PlayerCommand, CBC_ORDER_ANY);
MUTATOR_HOOK(SV_StartFrame, sandbox_StartFrame, CBC_ORDER_ANY);
- MUTATOR_HOOK(PlayerPreThink, sandbox_PlayerPreThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SetModname, sandbox_SetModname, CBC_ORDER_ANY);
MUTATOR_ONADD
{