]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/sandbox.qc
Merge branch 'master' of git://de.git.xonotic.org/xonotic/xonotic-data.pk3dir
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / sandbox.qc
index d20da7443e8fcf5e659205ce8ca8fbd73281547f..e7f51aa887f7931445f0006452db6110a7e8928f 100644 (file)
@@ -1,5 +1,6 @@
 const float MAX_STORAGE_ATTACHMENTS = 16;
 float object_count;
+.float object_flood;
 .entity object_attach;
 .string material;
 
@@ -29,22 +30,51 @@ void sandbox_ObjectFunction_Touch()
        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;
 }
@@ -55,11 +85,11 @@ void sandbox_ObjectEdit_Scale(entity e, float f)
        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)
 {
@@ -68,6 +98,7 @@ 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;
@@ -87,10 +118,6 @@ void sandbox_ObjectAttach_Remove(entity e)
                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;
@@ -98,6 +125,10 @@ void sandbox_ObjectAttach_Remove(entity e)
                        // 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;
                }
        }
 }
@@ -117,6 +148,8 @@ entity sandbox_ObjectSpawn(float database)
        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)
@@ -148,6 +181,14 @@ void sandbox_ObjectRemove(entity e)
 {
        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;        }
@@ -171,16 +212,18 @@ string sandbox_ObjectPort_Save(entity e, float database)
        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
                }
@@ -191,7 +234,7 @@ string sandbox_ObjectPort_Save(entity e, float database)
                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
                {
@@ -210,13 +253,14 @@ string sandbox_ObjectPort_Save(entity e, float database)
                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, "\" ");
@@ -258,7 +302,7 @@ entity sandbox_ObjectPort_Load(string s, float 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
                {
@@ -278,13 +322,14 @@ entity sandbox_ObjectPort_Load(string s, float database)
                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;
@@ -363,6 +408,7 @@ void sandbox_Database_Load()
                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)
@@ -371,9 +417,14 @@ 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;
                }
 
@@ -402,6 +453,7 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
                                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");
@@ -409,11 +461,18 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
                                print_to(self, "^7\"^2object_info ^3value^7\" shows public information about the object");
                                print_to(self, "^3object ^7- prints general information about the object, such as owner and creation / editing date");
                                print_to(self, "^3mesh ^7- prints information about the object's mesh, including skeletal bones");
+                               print_to(self, "^3attachments ^7- prints information about the object's attachments");
                                print_to(self, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects");
                                return TRUE;
 
                        // ---------------- 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"));
@@ -424,7 +483,7 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
                                        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;
@@ -472,6 +531,12 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
 
                                        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");
@@ -573,6 +638,18 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
                                                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))
                                                        {
@@ -664,13 +741,30 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
                                        switch(argv(2))
                                        {
                                                case "object":
-                                                       print_to(self, strcat("^2SANDBOX - INFO: ^7Object is owned by ^7", e.netname, "^7, created ^3", e.message, "^7, last edited ^3", e.message2));
+                                                       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":
-                                                       string tags;
-                                                       for(i = 1; gettaginfo(e, i); i++)
-                                                               tags = strcat(tags, "^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: ", tags));
+                                                       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
+                                                       entity head;
+                                                       for(head = world; (head = find(head, classname, "object")); )
+                                                       {
+                                                               if(head.owner == e)
+                                                               {
+                                                                       ++i; // start from 1
+                                                                       gettaginfo(e, head.tag_index);
+                                                                       s = strcat(s, "^1attachment ", ftos(i), "^7 has mesh \"^3", head.model, "^7\" at animation frame ^3", ftos(head.frame));
+                                                                       s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", ");
+                                                               }
+                                                       }
+                                                       if(i) // object contains attachments
+                                                               print_to(self, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(i), "^7 attachment(s): ", s));
+                                                       else
+                                                               print_to(self, "^2SANDBOX - INFO: ^7Object contains no attachments");
                                                        return TRUE;
                                        }
                                }
@@ -686,31 +780,6 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand)
        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)
 {
@@ -729,7 +798,6 @@ 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_ONADD
        {