├── .gitignore ├── LICENSE ├── README.md ├── entities └── entities │ └── nut_money.lua ├── gamemode ├── cl_init.lua ├── classes │ ├── sh_character.lua │ ├── sh_inventory.lua │ └── sh_item.lua ├── core │ ├── charvars │ │ ├── sh_class.lua │ │ ├── sh_data.lua │ │ ├── sh_desc.lua │ │ ├── sh_id.lua │ │ ├── sh_inv.lua │ │ ├── sh_model.lua │ │ ├── sh_money.lua │ │ ├── sh_name.lua │ │ ├── sh_owner.lua │ │ ├── sh_team.lua │ │ └── sh_var.lua │ ├── sh_components.lua │ └── sql │ │ ├── mysqloo.lua │ │ ├── sqlite.lua │ │ └── tmysql4.lua ├── init.lua ├── libraries │ ├── sh_character.lua │ ├── sh_chat.lua │ ├── sh_command.lua │ ├── sh_currency.lua │ ├── sh_data.lua │ ├── sh_item.lua │ ├── sh_plugin.lua │ ├── sh_team.lua │ ├── sv_character.lua │ └── sv_database.lua ├── shared.lua ├── thirdparty │ └── sh_pon.lua ├── util.lua └── utilities │ ├── cl_blur.lua │ ├── cl_resolution.lua │ ├── cl_text.lua │ ├── sh_door.lua │ ├── sh_entity.lua │ ├── sh_player.lua │ ├── sh_sound.lua │ └── sh_string.lua └── nutscript2.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.swo 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Brian Hang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NutScript 2 2 | NutScript 2 is a free roleplay gamemode framework. The intent of NutScript 2 is to provide some functions generally found in most roleplay gamemodes for Garry's Mod so starting a new gamemode is easier as you no longer have to spend a lot of time programming the base of a roleplay gamemode. NutScript 2 aims to provide some general functionality, so any gamemode could be created. The functions provided with NutScript 2 can be found on the [wiki](https://github.com/brianhang/nutscript2/wiki). 3 | 4 | # Usage 5 | To create your own roleplay gamemode using NutScript 2, first [set up the normal gamemode files](http://wiki.garrysmod.com/page/Gamemode_Creation). In the `cl_init.lua` and `init.lua` files, add the following at the top: `DeriveGamemode("nutscript2")`. This will make your gamemode derive from NutScript so the included function, classes, and hooks can be used. If you need to use features from another gamemode, set `NUT_BASE` to the name of the desired gamemode. For example, if you wanted to have a spawn menu and the tool gun, add `NUT_BASE = "sandbox"` before the call to `DeriveGamemode`. 6 | 7 | After you have derived from NutScript 2, a quick way to add functionality to your gamemode is through plugins. 8 | -------------------------------------------------------------------------------- /entities/entities/nut_money.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | -- Some basic information about the entity. 4 | ENT.Type = "anim" 5 | ENT.PrintName = "Money" 6 | ENT.Spawnable = false 7 | 8 | -- Set up the amount of money variable. 9 | function ENT:SetupDataTables() 10 | self:NetworkVar("Int", 0, "Amount") 11 | end 12 | 13 | if (SERVER) then 14 | -- The default size of the money entity in Source units. 15 | local SIZE = 8 16 | 17 | -- Called to set up the entity. 18 | function ENT:Initialize() 19 | -- Set the model. 20 | self:SetModel(nut.currency.model or "models/props_lab/box01a.mdl") 21 | self:SetSolid(SOLID_VPHYSICS) 22 | self:PhysicsInit(SOLID_VPHYSICS) 23 | 24 | -- Set use type to act like a button. 25 | self:SetUseType(SIMPLE_USE) 26 | 27 | -- Set up the physics for the entity. 28 | local physObj = self:GetPhysicsObject() 29 | 30 | if (IsValid(physObj)) then 31 | -- Allow the money entity to freely move. 32 | physObj:EnableMotion(true) 33 | physObj:Wake() 34 | else 35 | -- Set up a default size if the model for the entity 36 | -- is not working. 37 | local min, max = Vector(-SIZE, -SIZE, -SIZE), 38 | Vector(SIZE, SIZE, SIZE) 39 | 40 | self:PhysicsInitBox(min, max) 41 | self:SetCollisionBounds(min, max) 42 | end 43 | end 44 | 45 | -- Allow for money entities to combine. 46 | function ENT:StartTouch(other) 47 | if (other:GetClass() == self:GetClass()) then 48 | self:SetAmount(self:GetAmount() + other:GetAmount()) 49 | other:Remove() 50 | end 51 | end 52 | 53 | -- Allow players to pick up the money. 54 | function ENT:Use(client) 55 | local character = client:getChar() 56 | 57 | if (character) then 58 | if (hook.Run("PlayerPickupMoney", client, self) == false) then 59 | return 60 | end 61 | 62 | character:giveMoney(self:GetAmount()) 63 | self:Remove() 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /gamemode/cl_init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: cl_init.lua 3 | Purpose: Loads the client-side portion of NutScript. 4 | --]] 5 | 6 | -- Set up the NutScript "namespace". 7 | nut = nut or {} 8 | 9 | -- Place to store VGUI panels. 10 | nut.gui = nut.gui or {} 11 | 12 | -- Include shared.lua to load all the framework files then setup.lua to set up 13 | -- the gamemode for use. 14 | include("shared.lua") 15 | -------------------------------------------------------------------------------- /gamemode/classes/sh_character.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: classes/sh_character.lua 3 | Purpose: Defines the character class here which is used to store data 4 | related to a character. 5 | --]] 6 | 7 | -- The DT variable for character ID. 8 | CHAR_ID = 31 9 | 10 | -- The number of bits in a longer number. 11 | local LONG = 32 12 | 13 | local CHARACTER = nut.meta.character or {} 14 | CHARACTER.__index = CHARACTER 15 | CHARACTER.id = 0 16 | CHARACTER.vars = {} 17 | 18 | -- Finds a player whose active character matches a given ID. 19 | local function findPlayerByCharID(id) 20 | for _, client in ipairs(player.GetAll()) do 21 | if (client:GetDTInt(CHAR_ID) == id) then 22 | return client 23 | end 24 | end 25 | end 26 | 27 | -- Returns a string representation of the character. 28 | function CHARACTER:__tostring() 29 | return "character["..self.id.."]" 30 | end 31 | 32 | -- Returns whether or not two characters are equal by checking for ID. 33 | function CHARACTER:__eq(other) 34 | return self.id == other.id 35 | end 36 | 37 | -- Deallocates a character. 38 | function CHARACTER:destroy() 39 | nut.char.delete(self:getID(), nil, true) 40 | end 41 | 42 | -- Gets the numeric ID for the character. 43 | function CHARACTER:getID() 44 | return self.id 45 | end 46 | 47 | -- Micro-optimizations for getPlayer. 48 | local isValid = IsValid 49 | 50 | -- Gets the player that is the owner of the character. 51 | function CHARACTER:getPlayer() 52 | self.player = isValid(self.player) and 53 | self.player.getChar(self.player) == self and 54 | self.player or findPlayerByCharID(self.id) 55 | 56 | return self.player 57 | end 58 | 59 | if (SERVER) then 60 | -- Number of seconds in a minute. 61 | local MINUTE = 60 62 | 63 | -- Prevents this character from being used. 64 | function CHARACTER:ban(time, reason) 65 | -- Get when the ban will expire. 66 | local expiration = 0 67 | 68 | if (time) then 69 | time = tonumber(time) or MINUTE 70 | expiration = os.time() + time 71 | else 72 | time = 0 73 | end 74 | 75 | -- Store the expiration time. 76 | self:setData("ban", expiration) 77 | 78 | -- Store the reason as well. 79 | if (reason) then 80 | self:setData("banReason", tostring(reason)) 81 | end 82 | 83 | hook.Run("CharacterBanned", self, time, 84 | reason and tostring(reason) or "") 85 | end 86 | 87 | -- Deletes this character permanently. 88 | function CHARACTER:delete() 89 | nut.char.delete(self:getID()) 90 | end 91 | 92 | -- Ejects the owner of this character. 93 | function CHARACTER:kick(reason) 94 | -- Stop this character from being an active character for a player. 95 | local client = self:getPlayer() 96 | 97 | if (IsValid(client) and client:getChar() == self) then 98 | -- Set the player's active character to none. 99 | client:SetDTInt(CHAR_ID, 0) 100 | 101 | -- Default the reason to an empty string. 102 | if (type(reason) ~= "string") then 103 | reason = "" 104 | end 105 | 106 | hook.Run("CharacterKicked", self, client, reason) 107 | end 108 | end 109 | 110 | -- Saves the character to the database. 111 | function CHARACTER:save(callback) 112 | -- The data for the update query. 113 | local data = {} 114 | 115 | -- Save each applicable variable. 116 | for name, variable in pairs(nut.char.vars) do 117 | -- If onSave is given, overwrite the normal saving. 118 | if (type(variable.onSave) == "function") then 119 | variable.onSave(self) 120 | 121 | continue 122 | end 123 | 124 | -- Ignore constant variables and variables without SQL fields. 125 | if (not variable.field or variable.isConstant) then 126 | continue 127 | end 128 | 129 | -- Get the character's value for this variable. 130 | local value = self.vars[name] 131 | 132 | -- Make sure not null variables are not updated to null. 133 | if (variable.notNull and value == nil) then 134 | ErrorNoHalt("Tried to set not null '"..name.."' to null".. 135 | " for character #"..self.id.."!\n") 136 | 137 | continue 138 | end 139 | 140 | data[variable.field] = value 141 | end 142 | 143 | -- Run the update query. 144 | nut.db.update(CHARACTERS, data, "id = "..self.id, callback) 145 | 146 | hook.Run("CharacterSave", self) 147 | end 148 | 149 | -- Sets up a player to reflect this character. 150 | function CHARACTER:setup(client) 151 | assert(type(client) == "Player", "client is not a player") 152 | assert(IsValid(client), "client is not a valid player") 153 | 154 | -- Set the player's active character to be this character. 155 | client:SetDTInt(CHAR_ID, self:getID()) 156 | 157 | -- Set up all the character variables. 158 | for _, variable in pairs(nut.char.vars) do 159 | if (type(variable.onSetup) == "function") then 160 | variable.onSetup(self, client) 161 | end 162 | end 163 | 164 | hook.Run("CharacterSetup", self, client) 165 | end 166 | 167 | -- Networks the character data to the given recipient(s). 168 | function CHARACTER:sync(recipient, ignorePrivacy) 169 | -- Whether or not recipient is a table. 170 | local isTable = type(recipient) == "table" 171 | 172 | -- Default synchronizing to everyone. 173 | if (type(recipient) ~= "Player" and not isTable) then 174 | recipient = player.GetAll() 175 | isTable = true 176 | end 177 | 178 | -- Sync a list of players by syncing them individually. 179 | if (isTable) then 180 | for k, v in ipairs(recipient) do 181 | if (type(v) == "Player" and IsValid(v)) then 182 | self:sync(v, ignorePrivacy) 183 | end 184 | end 185 | 186 | return 187 | end 188 | 189 | assert(IsValid(recipient), "recipient is not a valid player") 190 | 191 | -- Synchronize all applicable variables. 192 | for name, variable in pairs(nut.char.vars) do 193 | -- Ignore variables that do not need any networking. 194 | if (variable.replication == CHARVAR_NONE) then 195 | continue 196 | end 197 | 198 | -- Keep private variables to the owner only. 199 | if (variable.replication == CHARVAR_PRIVATE and 200 | recipient ~= self:getPlayer() and not ignorePrivacy) then 201 | continue 202 | end 203 | 204 | -- Allow for custom synchronization. 205 | if (type(variable.onSync) == "function" and 206 | variable.onSync(self, recipient) == false) then 207 | continue 208 | end 209 | 210 | -- Network this variable. 211 | net.Start("nutCharVar") 212 | net.WriteInt(self:getID(), LONG) 213 | net.WriteString(name) 214 | net.WriteType(self.vars[name]) 215 | net.Send(recipient) 216 | end 217 | 218 | hook.Run("CharacterSync", self, recipient) 219 | end 220 | 221 | -- Allows the character to be used again. 222 | function CHARACTER:unban() 223 | -- Clear the ban status. 224 | if (self:getData("ban")) then 225 | self:setData("ban", nil) 226 | end 227 | 228 | -- Clear the ban reason. 229 | if (self:getData("banReason")) then 230 | self:setData("banReason", nil) 231 | end 232 | 233 | hook.Run("CharacterUnbanned", self) 234 | end 235 | end 236 | 237 | nut.meta.character = CHARACTER 238 | -------------------------------------------------------------------------------- /gamemode/classes/sh_inventory.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: classes/sh_inventory.lua 3 | Purpose: Defines the inventory class which is a container for items. 4 | --]] 5 | 6 | local INVENTORY = nut.meta.inventory or {} 7 | INVENTORY.__index = INVENTORY 8 | INVENTORY.owner = 0 9 | INVENTORY.id = 0 10 | 11 | -- Returns the string representation of the inventory. 12 | function INVENTORY:__tostring() 13 | return "inventory["..self.id.."]" 14 | end 15 | 16 | -- Returns the unique, numeric ID for the inventory. 17 | function INVENTORY:getID() 18 | return self.id 19 | end 20 | 21 | -- Returns the owning character's ID. 22 | function INVENTORY:getOwner() 23 | return self.owner 24 | end 25 | 26 | -- Adds an item to the inventory. 27 | function INVENTORY:add() 28 | error("INVENTORY:add() has not been overwritten!") 29 | end 30 | 31 | -- Deletes the inventory from existence along with its items. 32 | function INVENTORY:delete() 33 | error("INVENTORY:delete() has not been overwritten!") 34 | end 35 | 36 | -- Loads the items within an inventory from the database. 37 | function INVENTORY:load() 38 | error("INVENTORY:load() has not been overwritten!") 39 | end 40 | 41 | -- Removes an item to the inventory. 42 | function INVENTORY:remove() 43 | error("INVENTORY:remove() has not been overwritten!") 44 | end 45 | 46 | -- Saves all the items in the inventory. 47 | function INVENTORY:save() 48 | end 49 | 50 | -- Synchronizes the items within the inventory. 51 | function INVENTORY:sync() 52 | error("INVENTORY:sync() has not been overwritten!") 53 | end 54 | 55 | nut.meta.inventory = INVENTORY -------------------------------------------------------------------------------- /gamemode/classes/sh_item.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: classes/sh_item.lua 3 | Purpose: Creates the item class which is a usable object for a character. 4 | --]] 5 | 6 | local ITEM = nut.meta.item or {} 7 | ITEM.__index = ITEM 8 | ITEM.id = "unknown" 9 | ITEM.name = "Unknown" 10 | ITEM.model = "models/error.mdl" 11 | 12 | -- Returns the string representation of the item. 13 | function ITEM:__tostring() 14 | return "item["..self.id.."]" 15 | end 16 | 17 | -- Deletes an item from existence. 18 | function ITEM:delete() 19 | error("ITEM:delete() has not been overwritten!") 20 | end 21 | 22 | nut.meta.item = ITEM 23 | -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_class.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_class.lua 3 | Purpose: Allows characters to be divided into classes within a team. 4 | --]] 5 | 6 | -- Alias for class data value. 7 | nut.char.registerVar("class", { 8 | default = 0, 9 | onGet = function(character) 10 | return character:getData("class", 0) 11 | end, 12 | onSet = function(character, value) 13 | character:setData("class", value) 14 | 15 | return false 16 | end, 17 | replication = CHARVAR_NONE 18 | }) -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_data.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_data.lua 3 | Purpose: Allows for arbitrary data to be stored for characters. 4 | --]] 5 | 6 | -- The open curly brace position. 7 | local TABLE_START = 2 8 | 9 | -- The close curly brace position. 10 | local TABLE_END = -2 11 | 12 | -- The number of bits in a long. 13 | local LONG = 32 14 | 15 | nut.char.registerVar("data", { 16 | default = {}, 17 | onDelete = function(id) 18 | assert(type(id) == "number", "ID must be a number") 19 | 20 | nut.db.delete(CHAR_DATA, "id = "..id) 21 | end, 22 | onLoad = function(character) 23 | local id = character:getID() 24 | 25 | -- Load all the previous set data values. 26 | nut.db.select(CHAR_DATA, {"key", "value"}, "id = "..id, function(data) 27 | if (not data) then 28 | return 29 | end 30 | 31 | -- Store all the data into the character's data. 32 | for i = 1, #data do 33 | local key = data[i].key 34 | local value = data[i].value 35 | local status, result = pcall(pon.decode, "{"..value.."}") 36 | 37 | if (status) then 38 | character.vars.data[key] = result[1] 39 | end 40 | end 41 | end) 42 | end, 43 | onSet = function(character, key, value, recipient) 44 | -- Store the old value for the hook. 45 | local oldValue = character.vars.data[key] 46 | 47 | -- Set the data value. 48 | character.vars.data[key] = value 49 | 50 | -- Update the character data in the database. 51 | local query 52 | 53 | if (value ~= nil) then 54 | -- Get the encoded data. 55 | local status, result = pcall(pon.encode, {value}) 56 | 57 | if (not status) then 58 | ErrorNoHalt("Failed to set data '"..key.."' to '".. 59 | tostring(value).."' due to encoding error!\n") 60 | 61 | return false 62 | end 63 | 64 | local encoded = result:sub(TABLE_START, TABLE_END) 65 | 66 | -- Create a query to update the data. 67 | query = "REPLACE INTO "..CHAR_DATA.." (`id`, `key`, `value`) ".. 68 | "VALUES (%s, '%s', '%s')" 69 | query = query:format(character:getID(), 70 | nut.db.escape(tostring(key)), 71 | nut.db.escape(encoded)) 72 | else 73 | -- Delete if nil since storing it is not needed. 74 | query = "DELETE FROM "..CHAR_DATA.." WHERE `id`=%s AND `key`='%s'" 75 | query = query:format(character:getID(), 76 | nut.db.escape(tostring(key))) 77 | end 78 | 79 | nut.db.query(query) 80 | 81 | -- Don't do any networking if it is not wanted. 82 | if (recipient == false) then 83 | return false 84 | end 85 | 86 | net.Start("nutCharData") 87 | net.WriteInt(character:getID(), LONG) 88 | net.WriteString(key) 89 | net.WriteType(value) 90 | 91 | -- Determine who to send the variable to. 92 | if (not recipient and IsValid(character:getPlayer())) then 93 | net.Send(character:getPlayer()) 94 | elseif (type(recipient) == "table" or type(recipient) == "Player") then 95 | net.Send(recipient) 96 | else 97 | net.Broadcast() 98 | end 99 | 100 | hook.Run("CharacterDataChanged", character, key, oldValue, value) 101 | 102 | -- Don't do anything else after. 103 | return false 104 | end, 105 | onGet = function(character, key, default) 106 | return character.vars.data[key] or default 107 | end 108 | }) 109 | 110 | -- Handle networking of character data. 111 | if (SERVER) then 112 | util.AddNetworkString("nutCharData") 113 | else 114 | -- Sets a character data value. 115 | net.Receive("nutCharData", function() 116 | -- Read the information. 117 | local id = net.ReadInt(LONG) 118 | local key = net.ReadString() 119 | local value = net.ReadType() 120 | 121 | -- Get the character from the ID. 122 | local character = nut.char.list[id] or nut.char.new(id) 123 | local oldValue = character.vars.data[key] 124 | 125 | -- Update the data value. 126 | character.vars.data[key] = value 127 | hook.Run("CharacterDataChanged", character, key, oldValue, value) 128 | end) 129 | end 130 | -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_desc.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_desc.lua 3 | Purpose: Adds a physical description of a character. 4 | --]] 5 | 6 | nut.char.registerVar("desc", { 7 | default = "", 8 | field = "desc", 9 | replication = CHARVAR_PUBLIC 10 | }) -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_id.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_id.lua 3 | Purpose: Adds a numeric identifier for a character. 4 | --]] 5 | 6 | nut.char.registerVar("id", { 7 | field = "id", 8 | isConstant = true, 9 | onSave = CHARVAR_NOSAVE, 10 | replication = CHARVAR_NONE 11 | }) -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_inv.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_inv.lua 3 | Purpose: Adds the ability to store inventories for characters. 4 | --]] 5 | 6 | -- The number of bits in a long. 7 | local LONG = 32 8 | 9 | nut.char.registerVar("inv", { 10 | default = {}, 11 | isConstant = true, 12 | onLoad = function(character) 13 | -- Network and store the restored inventory. 14 | local function setupInventory(inventory, index) 15 | if (not character) then 16 | return 17 | end 18 | 19 | -- Store the inventory in the character. 20 | character.vars.inv[index] = inventory 21 | end 22 | 23 | -- Load all the inventories that belongs to the character. 24 | nut.item.restoreInvFromOwner(character:getID(), 25 | function(inventory, index) 26 | -- Setup the inventory for use when loaded. If the character does 27 | -- not have a inventory, create one. 28 | if (inventory) then 29 | setupInventory(inventory, index) 30 | else 31 | local owner = character:getID() 32 | 33 | if (hook.Run("InventoryInitialCreation", owner) ~= false) then 34 | nut.item.createInv(owner, function(inventory) 35 | setupInventory(inventory, 1) 36 | end) 37 | end 38 | end 39 | end) 40 | end, 41 | onSave = function(character) 42 | -- Save all the character's inventories. 43 | for _, inventory in ipairs(character.vars.inv) do 44 | if (type(inventory.save) == "function") then 45 | inventory:save() 46 | end 47 | end 48 | 49 | return false 50 | end, 51 | onGet = function(character, index) 52 | if (type(index) ~= "number") then 53 | index = 1 54 | end 55 | 56 | return character.vars.inv[index] 57 | end, 58 | onDestroy = function(id) 59 | -- Remove the inventory instance. 60 | nut.item.inventories[id] = nil 61 | 62 | -- Remove replications of the inventory on clients. 63 | net.Start("nutInventoryDestroy") 64 | net.WriteInt(id, LONG) 65 | net.Broadcast() 66 | end, 67 | onSync = function(character, client) 68 | for index, inventory in pairs(character.vars.inv) do 69 | -- Send the inventory information to the player. 70 | net.Start("nutInventoryInstance") 71 | net.WriteInt(inventory:getID(), LONG) 72 | net.WriteInt(inventory:getOwner(), LONG) 73 | net.WriteInt(index, LONG) 74 | net.Send(client) 75 | 76 | -- After, send all the items to the player. 77 | inventory:sync(client) 78 | end 79 | 80 | return false 81 | end, 82 | replication = CHARVAR_PRIVATE 83 | }) 84 | 85 | -- Handling networking for inventories. 86 | if (SERVER) then 87 | util.AddNetworkString("nutInventoryInstance") 88 | util.AddNetworkString("nutInventoryDestroy") 89 | else 90 | -- Replicate an inventory instance from the server. 91 | net.Receive("nutInventoryInstance", function() 92 | local id = net.ReadInt(LONG) 93 | local owner = net.ReadInt(LONG) 94 | local index = net.ReadInt(LONG) 95 | 96 | -- Store it in the given owner. 97 | local character = nut.char.list[owner] 98 | 99 | if (character) then 100 | character.vars.inv[index] = nut.item.newInv(id, owner) 101 | end 102 | end) 103 | 104 | -- Destroy an inventory instance. 105 | net.Receive("nutInventoryDestroy", function() 106 | local id = net.ReadInt(LONG) 107 | 108 | nut.item.inventories[id] = nil 109 | end) 110 | end -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_model.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_model.lua 3 | Purpose: Allows characters to be represented as a model in-game. 4 | --]] 5 | 6 | nut.char.registerVar("model", { 7 | default = "models/error.mdl", 8 | field = "model", 9 | onSet = function(character, value) 10 | local client = character:getPlayer() 11 | 12 | if (IsValid(client)) then 13 | client:SetModel(value) 14 | client:SetupHands() 15 | end 16 | end, 17 | onSetup = function(character, client) 18 | client:SetModel(character:getModel()) 19 | client:SetupHands() 20 | end, 21 | replication = CHARVAR_PUBLIC 22 | }) -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_money.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_money.lua 3 | Purpose: Allows characters to have a form of currency. 4 | --]] 5 | 6 | nut.char.registerVar("money", { 7 | default = 0, 8 | field = "money" 9 | }) -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_name.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_name.lua 3 | Purpose: Adds a name for characters. 4 | --]] 5 | 6 | nut.char.registerVar("name", { 7 | default = "", 8 | field = "name", 9 | replication = CHARVAR_PUBLIC, 10 | notNull = true 11 | }) 12 | 13 | -- Have players' name return their character's name. 14 | local PLAYER = FindMetaTable("Player") 15 | 16 | -- Allow for their Steam name to still be accessed. 17 | PLAYER.steamName = PLAYER.steamName or PLAYER.Name 18 | PLAYER.SteamName = PLAYER.steamName 19 | 20 | -- Micro-optimizations for getting the name. 21 | local charGetName = nut.meta.character.getName 22 | local steamName = PLAYER.steamName 23 | 24 | -- Return the character's name if there is one. 25 | function PLAYER:Name() 26 | local character = self.getChar(self) 27 | 28 | return character and charGetName(character) or steamName(self) 29 | end 30 | 31 | -- Have Nick and GetName do the same. 32 | PLAYER.Nick = PLAYER.Name 33 | PLAYER.GetName = PLAYER.Name -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_owner.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_owner.lua 3 | Purpose: Keeps track of character ownership for players. 4 | --]] 5 | 6 | nut.char.registerVar("owner", { 7 | default = "", 8 | field = "steamID", 9 | onSet = function(character, steamID) 10 | -- Convert players to a 64-bit SteamID. 11 | if (type(steamID) == "Player") then 12 | if (not IsValid(steamID)) then 13 | error("The new owner is not a valid player") 14 | end 15 | 16 | steamID = steamID:SteamID64() or 0 17 | elseif (type(steamID) ~= "string") then 18 | error("The new owner must be a player or a string") 19 | end 20 | 21 | -- Remove the old owner. 22 | local oldOwner = character:getPlayer() 23 | 24 | if (IsValid(oldOwner) and oldOwner:getChar() == character) then 25 | character:kick() 26 | end 27 | 28 | hook.Run("CharacterTransferred", character, oldOwner, steamID) 29 | 30 | -- Update the database immediately. 31 | steamID = nut.db.escape(steamID) 32 | nut.db.query("UPDATE "..CHARACTERS.." SET steamID = "..steamID.. 33 | " WHERE id = "..character:getID()) 34 | 35 | -- Override owner to be the SteamID if it was a player. 36 | return true, steamID 37 | end, 38 | onSave = CHARVAR_NOSAVE, 39 | replication = CHARVAR_PUBLIC 40 | }) -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_team.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_team.lua 3 | Purpose: Allows characters to be associated with certain teams. 4 | --]] 5 | 6 | nut.char.registerVar("team", { 7 | default = 0, 8 | field = "team", 9 | onSet = function(character, value) 10 | local client = character:getPlayer() 11 | 12 | if (IsValid(client)) then 13 | client:SetTeam(value) 14 | end 15 | end, 16 | onSetup = function(character, client) 17 | if (team.Valid(character:getTeam())) then 18 | client:SetTeam(character:getTeam()) 19 | end 20 | end, 21 | replication = CHARVAR_PUBLIC 22 | }) 23 | -------------------------------------------------------------------------------- /gamemode/core/charvars/sh_var.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/charvars/sh_var.lua 3 | Purpose: Allows for networked, temporary variables to be set for characters. 4 | --]] 5 | 6 | -- How many bits are in a long. 7 | local LONG = 32 8 | 9 | nut.char.registerVar("var", { 10 | default = {}, 11 | onSet = function(character, key, value, recipient) 12 | -- Store the old value for the hook. 13 | local oldValue = character.vars.var[key] 14 | 15 | -- Set the temporary variable. 16 | character.vars.var[key] = value 17 | 18 | -- If no recipient is desired, don't network the variable. 19 | if (recipient == false) then 20 | return 21 | end 22 | 23 | net.Start("nutCharTempVar") 24 | net.WriteInt(character:getID(), LONG) 25 | net.WriteString(key) 26 | net.WriteType(value) 27 | 28 | -- Determine who to send the variable to. 29 | if (not recipient and IsValid(character:getPlayer())) then 30 | net.Send(character:getPlayer()) 31 | elseif (type(recipient) == "table" or type(recipient) == "Player") then 32 | net.Send(recipient) 33 | else 34 | net.Broadcast() 35 | end 36 | 37 | hook.Run("CharacterTempVarChanged", character, key, oldValue, value) 38 | 39 | -- Don't do anything else after. 40 | return false 41 | end, 42 | onGet = function(character, key, default) 43 | return character.vars.var[key] or default 44 | end 45 | }) 46 | 47 | if (SERVER) then 48 | util.AddNetworkString("nutCharTempVar") 49 | else 50 | -- Sets a character temporary variable. 51 | net.Receive("nutCharTempVar", function() 52 | -- Read the information. 53 | local id = net.ReadInt(LONG) 54 | local key = net.ReadString() 55 | local value = net.ReadType() 56 | 57 | -- Get the character from the ID. 58 | local character = nut.char.list[id] or nut.char.new(id) 59 | local oldValue = character.vars.var[key] 60 | 61 | -- Update the data value. 62 | character.vars.var[key] = value 63 | hook.Run("CharacterTempVarChanged", character, key, oldValue, value) 64 | end) 65 | end -------------------------------------------------------------------------------- /gamemode/core/sh_components.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/sh_components.lua 3 | Purpose: Sets up plugin components for the built in libraries included with 4 | Nutscript. 5 | --]] 6 | 7 | -- For file names without the extension. 8 | local EXT_START = -5 9 | 10 | -- Includes entity files that are in a folder. 11 | local function includeEntityFiles(path, clientOnly) 12 | -- Include the entity if it includes a init.lua and cl_init.lua 13 | if (SERVER and file.Exists(path.."/init.lua", "LUA") or CLIENT) then 14 | -- Include the serverside portion. 15 | nut.util.include(path.."/init.lua", clientOnly and "client" or "server") 16 | 17 | -- Include the clientside portion. 18 | if (file.Exists(path.."/cl_init.lua", "LUA")) then 19 | nut.util.include(path.."/cl_init.lua") 20 | 21 | return true 22 | end 23 | 24 | -- Effects only need an init.lua so end the including here. 25 | if (clientOnly) then 26 | return true 27 | end 28 | end 29 | 30 | -- If the folder only has a shared.lua, include it. 31 | if (file.Exists(path.."/shared.lua", "LUA")) then 32 | nut.util.include(path.."/shared.lua", "shared") 33 | 34 | return true 35 | end 36 | 37 | return false 38 | end 39 | 40 | -- Includes all entities within a given folder. 41 | local function includeEntities(path, folder, variable, registerFunc, 42 | defaultInfo, clientOnly) 43 | defaultInfo = defaultInfo or {} 44 | 45 | -- Find all the entities. 46 | local files, folders = file.Find(path.."/"..folder.."/*", "LUA") 47 | 48 | -- Include all the folder entities. 49 | for _, entity in ipairs(folders) do 50 | local entityPath = path.."/"..folder.."/"..entity 51 | 52 | -- Include the entity file. 53 | _G[variable] = table.Copy(defaultInfo) 54 | _G[variable].ClassName = entity 55 | 56 | -- Register the entity after including it. 57 | if (includeEntityFiles(entityPath, clientOnly) and 58 | (clientOnly and CLIENT or not clientOnly)) then 59 | registerFunc(_G[variable], entity) 60 | end 61 | _G[variable] = nil 62 | end 63 | 64 | -- Include all the single file entities. 65 | for _, entity in ipairs(files) do 66 | local className = string.StripExtension(entity) 67 | 68 | _G[variable] = table.Copy(defaultInfo) 69 | _G[variable].ClassName = className 70 | 71 | -- Include the entity. 72 | nut.util.include(path.."/"..folder.."/"..entity, 73 | clientOnly and "client" or "shared") 74 | 75 | -- Register the entity. 76 | if (clientOnly and CLIENT or not clientOnly) then 77 | registerFunc(_G[variable], className) 78 | end 79 | _G[variable] = nil 80 | end 81 | end 82 | 83 | nut.plugin.addComponent("item", { 84 | onInclude = function(plugin, path) 85 | nut.item.loadFromDir(path) 86 | end 87 | }) 88 | 89 | nut.plugin.addComponent("team", { 90 | onInclude = function(plugin, path) 91 | nut.team.loadFromDir(path) 92 | end 93 | }) 94 | 95 | nut.plugin.addComponent("entity", { 96 | onInclude = function(plugin, path) 97 | path = path.."/entities" 98 | 99 | -- Include scripted entities. 100 | includeEntities(path, "entities", "ENT", scripted_ents.Register, { 101 | Type = "anim", 102 | Base = "base_gmodentity" 103 | }) 104 | 105 | -- Include scripted weapons. 106 | includeEntities(path, "weapons", "SWEP", weapons.Register, { 107 | Primary = {}, 108 | Secondary = {}, 109 | Base = "weapon_base" 110 | }) 111 | 112 | -- Include scripted effects. 113 | includeEntities(path, "effects", "EFFECT", effects and effects.Register, 114 | nil, true) 115 | end 116 | }) 117 | 118 | nut.plugin.addComponent("libraries", { 119 | onInclude = function(plugin, path) 120 | for _, library in ipairs(file.Find(path.."/libraries/*.lua", "LUA")) do 121 | nut.util.include(path.."/libraries/"..library) 122 | end 123 | end 124 | }) -------------------------------------------------------------------------------- /gamemode/core/sql/mysqloo.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/sql/mysqloo.lua 3 | Purpose: Provides an implementation for the NutScript database functions 4 | using mysqloo. 5 | --]] 6 | 7 | -- The position of the query within a queued query. 8 | local QUEUE_QUERY = 1 9 | 10 | -- The position of the callback within a queued query. 11 | local QUEUE_CALLBACK = 2 12 | 13 | -- A list of queries to run after a connection is restarted. 14 | nut.db.queryQueue = nut.db.queryQueue or {} 15 | 16 | nut.db.modules.mysqloo = { 17 | connect = function(callback) 18 | -- Open a database connection. 19 | if (not mysqloo) then 20 | require("mysqloo") 21 | end 22 | 23 | if (nut.db.object) then 24 | return 25 | end 26 | 27 | local object = mysqloo.connect(nut.db.hostname, nut.db.username, 28 | nut.db.password, nut.db.database, 29 | nut.db.port) 30 | 31 | function object:onConnected() 32 | hook.Run("DatabaseConnected") 33 | 34 | if (type(callback) == "function") then 35 | callback(true) 36 | end 37 | 38 | -- Run old queues. 39 | for _, query in ipairs(nut.db.queryQueue) do 40 | nut.db.query(query[QUEUE_QUERY], query[QUEUE_CALLBACK]) 41 | end 42 | 43 | nut.db.queryQueue = {} 44 | end 45 | 46 | function object:onConnectionFailed(reason) 47 | nut.db.lastError = reason 48 | hook.Run("DatabaseConnectionFailed") 49 | 50 | if (type(callback) == "function") then 51 | callback() 52 | end 53 | end 54 | 55 | -- Start the connection. 56 | object:connect() 57 | 58 | -- Store the database connection. 59 | nut.db.object = object 60 | end, 61 | query = function(value, callback) 62 | if (nut.db.object) then 63 | local query = nut.db.object:query(value) 64 | 65 | function query:onSuccess(data) 66 | nut.db.lastID = self:lastInsert() 67 | 68 | if (type(callback) == "function") then 69 | callback(data) 70 | end 71 | end 72 | 73 | function query:onError(reason) 74 | -- Reconnect if the connection timed out. 75 | if (reason:find("has gone away")) then 76 | -- Queue the query. 77 | nut.db.queryQueue[#nut.db.queryQueue + 1] = {value, 78 | callback} 79 | 80 | -- Reconnect to the database. 81 | nut.db.object:abortAllQueries() 82 | nut.db.modules.mysqloo.connect() 83 | else 84 | nut.db.lastError = reason 85 | 86 | if (type(callback) == "function") then 87 | callback() 88 | end 89 | end 90 | 91 | ErrorNoHalt("Query failed! ("..value..")\n") 92 | ErrorNoHalt(reason.."\n") 93 | end 94 | 95 | query:start() 96 | elseif (type(callback) == "function") then 97 | callback() 98 | end 99 | end, 100 | escape = function(value) 101 | if (nut.db.object) then 102 | return nut.db.object:escape(value) 103 | end 104 | 105 | return sql.SQLStr(value, true) 106 | end, 107 | getInsertID = function() 108 | return nut.db.lastID 109 | end, 110 | needsDetails = true 111 | } 112 | -------------------------------------------------------------------------------- /gamemode/core/sql/sqlite.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/sql/sqlite.lua 3 | Purpose: Provides an implementation for the NutScript database functions 4 | using SQLite. 5 | --]] 6 | 7 | nut.db.modules.sqlite = { 8 | connect = function(callback) 9 | hook.Run("DatabaseConnected") 10 | 11 | -- No actual connection needed. 12 | if (type(callback) == "function") then 13 | callback(true) 14 | end 15 | end, 16 | query = function(value, callback) 17 | local result = sql.Query(value) 18 | 19 | if (result ~= false) then 20 | -- Run the callback with the given results. 21 | if (type(callback) == "function") then 22 | callback(result or {}) 23 | end 24 | else 25 | -- If there was an error, store it and run the callback with 26 | -- no results. 27 | nut.db.lastError = sql.LastError() 28 | 29 | if (type(callback) == "function") then 30 | callback() 31 | end 32 | 33 | ErrorNoHalt("Query failed! ("..value..")\n") 34 | ErrorNoHalt(nut.db.lastError.."\n") 35 | end 36 | end, 37 | escape = function(value) 38 | return sql.SQLStr(value, true) 39 | end, 40 | getInsertID = function() 41 | return tonumber(sql.QueryValue("SELECT last_insert_rowid()")) 42 | end 43 | } 44 | -------------------------------------------------------------------------------- /gamemode/core/sql/tmysql4.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: core/sql/sqlite.lua 3 | Purpose: Provides an implementation for the NutScript database functions 4 | using SQLite. 5 | --]] 6 | 7 | nut.db.modules.tmysql4 = { 8 | connect = function(callback) 9 | if (not tmysql) then 10 | require("tmysql4") 11 | end 12 | 13 | if (nut.db.object) then 14 | return 15 | end 16 | 17 | -- Create a connection. 18 | local object, reason = tmysql.Connect(nut.db.hostname, 19 | nut.db.username, 20 | nut.db.password, 21 | nut.db.database, 22 | nut.db.port) 23 | 24 | -- Check if the connection was successful or not. 25 | if (object) then 26 | nut.db.object = object 27 | hook.Run("DatabaseConnected") 28 | 29 | if (type(callback) == "function") then 30 | callback(true) 31 | end 32 | else 33 | nut.db.lastError = reason 34 | hook.Run("DatabaseConnectionFailed", reason) 35 | 36 | if (type(callback) == "function") then 37 | callback() 38 | end 39 | end 40 | end, 41 | query = function(value, callback) 42 | if (nut.db.object) then 43 | nut.db.object:Query(value, function(results) 44 | local result = results[1] 45 | local data 46 | 47 | if (result.status) then 48 | data = result.data 49 | nut.db.lastID = result.lastid 50 | else 51 | ErrorNoHalt("Query failed! ("..value..")\n") 52 | ErrorNoHalt(result.error.."\n") 53 | 54 | nut.db.lastError = result.error 55 | end 56 | 57 | if (type(callback) == "function") then 58 | callback(data) 59 | end 60 | end) 61 | elseif (type(callback) == "function") then 62 | callback() 63 | end 64 | end, 65 | escape = function(value) 66 | if (nut.db.object) then 67 | return nut.db.object:Escape(value) 68 | end 69 | 70 | return sql.SQLStr(value, true) 71 | end, 72 | getInsertID = function() 73 | return nut.db.lastID 74 | end, 75 | needsDetails = true 76 | } 77 | -------------------------------------------------------------------------------- /gamemode/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: init.lua 3 | Purpose: Loads the server-side portion of NutScript. 4 | --]] 5 | 6 | -- Set up the NutScript "namespace". 7 | nut = nut or {} 8 | 9 | -- Include shared.lua to load all the framework files then setup.lua to set up 10 | -- the gamemode for use. 11 | AddCSLuaFile("cl_init.lua") 12 | AddCSLuaFile("util.lua") 13 | AddCSLuaFile("shared.lua") 14 | 15 | include("shared.lua") 16 | -------------------------------------------------------------------------------- /gamemode/libraries/sh_character.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sh_character.lua 3 | Purpose: Creates functions to create and manage characters. 4 | --]] 5 | 6 | -- How many bits are in a long integer. 7 | local LONG = 32 8 | 9 | -- Get the second character within a string. 10 | local SECOND = 2 11 | 12 | -- Global enums for types of character variable networking. 13 | CHARVAR_PUBLIC = 0 14 | CHARVAR_PRIVATE = 1 15 | CHARVAR_NONE = 2 16 | 17 | -- Empty function for doing nothing when saving. 18 | CHARVAR_NOSAVE = function() end 19 | 20 | nut.char = nut.char or {} 21 | nut.char.list = nut.char.list or {} 22 | nut.char.vars = {} 23 | 24 | -- Deletes a character from existence. 25 | function nut.char.delete(id, callback, temporary) 26 | assert(type(id) == "number", "id is not a number") 27 | 28 | -- Remove the character object. 29 | nut.char.list[id] = nil 30 | 31 | if (SERVER) then 32 | -- Called after the character has been deleted. 33 | function deleted(success) 34 | if (type(callback) == "function") then 35 | callback(success) 36 | end 37 | 38 | hook.Run("CharacterDeleted", id) 39 | 40 | -- Destroy associated data. 41 | for name, variable in pairs(nut.char.vars) do 42 | if (type(variable.onDestroy) == "function") then 43 | variable.onDestroy(id) 44 | end 45 | end 46 | 47 | -- Notify the clients to remove the character. 48 | net.Start("nutCharDelete") 49 | net.WriteInt(id, LONG) 50 | net.Broadcast() 51 | end 52 | 53 | -- If temporary, skip to the deleted callback. 54 | if (temporary) then 55 | return deleted(true) 56 | end 57 | 58 | -- Remove the character entry in the database. 59 | nut.db.delete(CHARACTERS, "id = "..id, deleted) 60 | 61 | -- Delete associated character data. 62 | for name, variable in pairs(nut.char.vars) do 63 | if (type(variable.onDelete) == "function") then 64 | variable.onDelete(id) 65 | end 66 | end 67 | else 68 | hook.Run("CharacterDeleted", id) 69 | end 70 | end 71 | 72 | -- Creates a character object. 73 | function nut.char.new(id) 74 | assert(type(id) == "number", "id is not a number") 75 | 76 | -- Create a character object. 77 | -- Note vars is deep copied so there are no side effects. 78 | local character = setmetatable({ 79 | id = id, 80 | vars = {} 81 | }, nut.meta.character) 82 | 83 | -- Set the variables to their default values. 84 | for name, variable in pairs(nut.char.vars) do 85 | character.vars[name] = variable.onGetDefault() 86 | end 87 | 88 | -- Store the character for later use. 89 | nut.char.list[id] = character 90 | 91 | return character 92 | end 93 | 94 | -- Sets up a character variable for use. 95 | function nut.char.registerVar(name, info) 96 | assert(type(info) == "table", "info is not a table") 97 | 98 | -- Set some default values for the parameters. 99 | name = tostring(name) 100 | info.replication = info.replication or CHARVAR_PRIVATE 101 | 102 | -- Get the character metatable so we can add setters/getters. 103 | local character = nut.meta.character 104 | 105 | -- Get a CamelCase version of name. 106 | local upperName = name:sub(1, 1):upper()..name:sub(SECOND) 107 | 108 | -- Create a function to get the default value. 109 | if (type(info.onGetDefault) ~= "function") then 110 | if (type(info.default) == "table") then 111 | info.onGetDefault = function() return table.Copy(info.default) end 112 | else 113 | info.onGetDefault = function() return info.default end 114 | end 115 | end 116 | 117 | -- Store the default in the metatable. 118 | character.vars[name] = info.onGetDefault() 119 | 120 | -- Create a getter function. 121 | if (type(info.onGet) == "function") then 122 | character["get"..upperName] = info.onGet 123 | else 124 | character["get"..upperName] = function(self) 125 | return self.vars[name] 126 | end 127 | end 128 | 129 | -- Create the setter function. 130 | if (SERVER and not info.isConstant) then 131 | -- Whether or not info.set is a function. 132 | local customSet = type(info.onSet) == "function" 133 | 134 | -- Determine how the variable will be networked. 135 | local send 136 | 137 | if (info.replication == CHARVAR_PUBLIC) then 138 | send = net.Broadcast 139 | elseif (info.replication == CHARVAR_PRIVATE) then 140 | send = function(self) 141 | local client = self:getPlayer() 142 | 143 | if (IsValid(client)) then 144 | net.Send(client) 145 | end 146 | end 147 | elseif (type(info.replication) == "function") then 148 | send = info.replication 149 | end 150 | 151 | character["set"..upperName] = function(self, value, ...) 152 | -- Run the custom setter if given. 153 | if (customSet) then 154 | local override, newValue = info.onSet(self, value, ...) 155 | 156 | if (override) then 157 | value = newValue 158 | else 159 | return 160 | end 161 | end 162 | 163 | -- Get the current value before it is changed for the hook. 164 | local oldValue = self.vars[name] 165 | 166 | -- Store the given value. 167 | self.vars[name] = value 168 | 169 | -- Network the variable. 170 | if (send) then 171 | net.Start("nutCharVar") 172 | net.WriteInt(self:getID(), LONG) 173 | net.WriteString(name) 174 | net.WriteType(value) 175 | send(self) 176 | end 177 | 178 | hook.Run("CharacterVarChanged", self, name, oldValue, value) 179 | end 180 | end 181 | 182 | nut.char.vars[name] = info 183 | end 184 | 185 | -- Validates information for character creation. 186 | function nut.char.validateInfo(info, context) 187 | -- Default the context to an empty table. 188 | context = context or {} 189 | 190 | -- Check with the character variables. 191 | for key, value in pairs(info) do 192 | -- Get the variable that the key corresponds to. 193 | local variable = nut.char.vars[key] 194 | 195 | -- Make sure there are no invalid variables. 196 | if (not variable) then 197 | return false, "invalid variable ("..key..")" 198 | end 199 | 200 | -- Custom check with onValidate. 201 | if (type(variable.onValidate) == "function") then 202 | local valid, reason = variable.onValidate(info[key], context) 203 | 204 | if (valid == false) then 205 | return false, reason or "invalid value for "..key 206 | end 207 | end 208 | end 209 | 210 | -- Null check for variables. 211 | for name, variable in pairs(nut.char.vars) do 212 | if (variable.notNull and not info[name]) then 213 | return false, name.." was not provided" 214 | end 215 | end 216 | 217 | -- Use the CharacterValidateInfo hook to check. 218 | local valid, fault = hook.Run("CharacterValidateInfo", info, context) 219 | 220 | if (valid == false) then 221 | return false, fault 222 | end 223 | 224 | return true 225 | end 226 | 227 | -- Set up character variables. 228 | nut.util.includeDir("core/charvars") 229 | 230 | -- Create a function to get characters from players. 231 | local PLAYER = FindMetaTable("Player") 232 | 233 | function PLAYER:getChar() 234 | return nut.char.list[self:GetDTInt(CHAR_ID)] 235 | end 236 | 237 | -- Handle networking of character variables. 238 | if (CLIENT) then 239 | -- Removes a character on the client. 240 | net.Receive("nutCharDelete", function() 241 | -- Get the character that is being removed. 242 | local id = net.ReadInt(LONG) 243 | 244 | -- Remove the character. 245 | nut.char.delete(id) 246 | hook.Run("CharacterDeleted", id) 247 | end) 248 | 249 | -- Sets a character variable. 250 | net.Receive("nutCharVar", function() 251 | -- Read the information. 252 | local id = net.ReadInt(LONG) 253 | local key = net.ReadString() 254 | local value = net.ReadType() 255 | 256 | -- Get the character from the ID. 257 | local character = nut.char.list[id] or nut.char.new(id) 258 | local oldValue = character.vars[key] 259 | 260 | -- Update the variable. 261 | character.vars[key] = value 262 | hook.Run("CharacterVarChanged", character, key, oldValue, value) 263 | end) 264 | end -------------------------------------------------------------------------------- /gamemode/libraries/sh_chat.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sh_chat.lua 3 | Purpose: Sets up a system to divide chat messages into different types. 4 | --]] 5 | 6 | nut.chat = {} 7 | nut.chat.modes = {} 8 | 9 | -- The first character after a chat mode prefix. 10 | local MSG_START = 2 11 | 12 | -- A function that always returns true. 13 | local ALWAYS_TRUE = function() return true end 14 | 15 | -- Gets a list of players that can receive a certain chat. 16 | function nut.chat.getRecipients(speaker, mode, message, context) 17 | assert(type(speaker) == "Player", "speaker is not a player") 18 | assert(IsValid(speaker), "speaker is not a valid player") 19 | 20 | -- Default context to an empty table. 21 | context = context or {} 22 | 23 | -- Get information about the chat mode. 24 | local info = nut.chat.modes[mode] 25 | 26 | -- A list of players that can hear the speaker. 27 | local recipients = {} 28 | 29 | if (not info) then 30 | return recipients 31 | end 32 | 33 | -- Find which players can hear the speaker. 34 | for _, listener in ipairs(player.GetAll()) do 35 | if (info.onCanHear(speaker, listener, message, context) or 36 | hook.Run("IsChatRecipient", speaker, listener, mode, 37 | message, context)) then 38 | recipients[#recipients + 1] = listener 39 | end 40 | end 41 | 42 | return recipients 43 | end 44 | 45 | -- Determines the type of chat message being sent from a given message. 46 | function nut.chat.parse(speaker, message) 47 | assert(type(speaker) == "Player", "speaker is not a player") 48 | assert(IsValid(speaker), "speaker is not a valid player") 49 | assert(type(message) == "string") 50 | 51 | -- A table to store information about the chat message. 52 | local mode 53 | local context = {} 54 | 55 | -- Find the chat mode that matches. 56 | for thisMode, info in pairs(nut.chat.modes) do 57 | local prefix = info.prefix 58 | local offset = info.noSpaceAfter and 0 or 1 59 | local noSpaceAfter = info.noSpaceAfter 60 | 61 | -- Check if the prefix for this chat mode matches the one in 62 | -- the chat message. 63 | if (type(prefix) == "table") then 64 | -- If the prefix has aliases, check those as well. 65 | for _, alias in pairs(prefix) do 66 | if (message:sub(1, #alias + offset) == 67 | alias..(noSpaceAfter and "" or " ")) then 68 | mode = thisMode 69 | prefix = alias 70 | 71 | break 72 | end 73 | end 74 | elseif (type(prefix) == "string" and message:sub(1, #prefix + offset) 75 | == prefix..(noSpaceAfter and "" or " ")) then 76 | mode = thisMode 77 | end 78 | 79 | -- Change the message to only contain the real chat message. 80 | if (mode == thisMode) then 81 | message = message:sub(#prefix + 1) 82 | 83 | -- Get rid of the space in front if needed. 84 | if (noSpaceAfter and message:sub(1, 1):match("%s")) then 85 | message = message:sub(MSG_START) 86 | elseif (not noSpaceAfter) then 87 | message = message:sub(MSG_START) 88 | end 89 | end 90 | end 91 | 92 | -- Default the chat mode to unknown. 93 | mode = mode or "" 94 | 95 | -- Adjust the mode, context, and message. 96 | mode = hook.Run("ChatAdjustMode", speaker, mode, message, context) or mode 97 | hook.Run("ChatAdjustContext", speaker, mode, message, context) 98 | 99 | return mode, message, context 100 | end 101 | 102 | -- Sets up a chat mode for use in the future. 103 | function nut.chat.register(mode, info) 104 | mode = tostring(mode) 105 | assert(type(info) == "table", "info for '"..mode.."' is not a table") 106 | 107 | -- Convert the canHear function into one that returns whether or not 108 | -- a player can be heard as a boolean. 109 | if (type(info.onCanHear) == "number" or type(info.range) == "number") then 110 | local range = info.onCanHear or info.range 111 | 112 | info.onCanHear = function(speaker, listener) 113 | return speaker:GetPos():Distance(listener:GetPos()) <= range 114 | end 115 | else 116 | info.onCanHear = ALWAYS_TRUE 117 | end 118 | 119 | -- Default the prefix to the mode's identifier. 120 | info.prefix = info.prefix or "/"..mode 121 | 122 | -- Default the onCanSay to always return true. 123 | info.onCanSay = info.onCanSay or ALWAYS_TRUE 124 | 125 | -- Default the adding chat to the chatbox to using a format and color. 126 | if (CLIENT and type(info.onChatAdd) ~= "function") then 127 | info.color = info.color or color_white 128 | info.format = info.format or "%s: %s" 129 | 130 | -- Create a function to add the message to the chatbox. 131 | info.onChatAdd = function(speaker, message, context) 132 | local name = hook.Run("ChatGetName", speaker, message, context) or 133 | speaker:Name() 134 | 135 | chat.AddText(info.color, info.format:format(name, message)) 136 | end 137 | end 138 | 139 | -- Store the chat mode. 140 | nut.chat.modes[mode] = info 141 | end 142 | 143 | -- Sends a chat message using a given mode. 144 | function nut.chat.send(speaker, message) 145 | assert(type(speaker) == "Player", "speaker is not a player") 146 | assert(IsValid(speaker), "speaker is not a valid player") 147 | assert(type(message) == "string", "message is not a string") 148 | 149 | -- Find the chat mode. 150 | local mode, message, context = nut.chat.parse(speaker, message) 151 | 152 | -- If one was not found, return false since the message will not 153 | -- be sent. 154 | if (not nut.chat.modes[mode]) then 155 | return false 156 | end 157 | 158 | -- Allow for final adjustments to the message. 159 | message = hook.Run("ChatMessageAdjust", speaker, mode, message) or message 160 | 161 | -- Network the chat message. 162 | local recipients = nut.chat.getRecipients(speaker, mode, message, context) 163 | 164 | if (#recipients > 0) then 165 | net.Start("nutChatMsg") 166 | net.WriteEntity(speaker) 167 | net.WriteString(mode) 168 | net.WriteString(message) 169 | net.WriteTable(context) 170 | net.Send(recipients) 171 | end 172 | 173 | return true 174 | end 175 | 176 | if (SERVER) then 177 | util.AddNetworkString("nutChatMsg") 178 | else 179 | net.Receive("nutChatMsg", function() 180 | local speaker = net.ReadEntity() 181 | local mode = net.ReadString() 182 | local message = net.ReadString() 183 | local context = net.ReadTable() 184 | local info = nut.chat.modes[mode] 185 | 186 | if (info) then 187 | info.onChatAdd(speaker, message, context) 188 | 189 | if (hook.Run("ShouldChatTick") ~= false) then 190 | chat.PlaySound() 191 | end 192 | end 193 | end) 194 | end 195 | -------------------------------------------------------------------------------- /gamemode/libraries/sh_command.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sh_command.lua 3 | Purpose: Allows for chat and console commands to be easily set up. 4 | --]] 5 | 6 | nut.command = {} 7 | nut.command.list = {} 8 | nut.command.prefix = "nut" 9 | 10 | -- The prefix for chat commands. 11 | local COMMAND_PREFIX = "/" 12 | 13 | -- The pattern for command names. 14 | local COMMAND_PATTERN = "([_%w]+)" 15 | 16 | -- The cooldown for processing commands from a player. 17 | local COMMAND_COOLDOWN = 0.1 18 | 19 | -- Where the arguments are after a command. 20 | local ARGS_START = 2 21 | 22 | -- Aways return true. 23 | local ALWAYS_TRUE = function() return true end 24 | 25 | -- Adds a command so it can be used. 26 | function nut.command.add(name, info) 27 | assert(type(name) == "string", "name is not a string") 28 | assert(type(info) == "table", "info is not a table") 29 | assert(type(info.onRun) == "function", "'"..name.."' does not".. 30 | " have a onRun function") 31 | assert(name:match(COMMAND_PATTERN), "name can only contain".. 32 | " alphanumeric characters and underscores") 33 | 34 | -- Set up command permission shortcuts. 35 | if (type(info.onCanRun) ~= "function") then 36 | if (info.adminOnly) then 37 | info.onCanRun = function(client) 38 | return client:IsAdmin() 39 | end 40 | elseif (info.superAdminOnly) then 41 | info.onCanRun = function(client) 42 | return client:IsSuperAdmin() 43 | end 44 | elseif (type(info.group) == "string") then 45 | info.onCanRun = function(client) 46 | return client:IsUserGroup(info.group) 47 | end 48 | elseif (type(info.group) == "table") then 49 | info.onCanRun = function(client) 50 | -- Check each group in the list of groups. 51 | for _, group in ipairs(info.group) do 52 | if (client:IsUserGroup(group)) then 53 | return true 54 | end 55 | end 56 | 57 | return false 58 | end 59 | else 60 | info.onCanRun = ALWAYS_TRUE 61 | end 62 | end 63 | 64 | -- Add a default empty syntax. 65 | info.syntax = info.syntax or "" 66 | 67 | -- Add the command. 68 | nut.command.list[name] = info 69 | 70 | hook.Run("CommandAdded", name, info) 71 | end 72 | 73 | -- Returns whether or not a command can be ran by a player. 74 | function nut.command.canRun(client, command) 75 | assert(type(command) == "string", "command is not a string") 76 | 77 | -- Get information about the given command. 78 | local info = nut.command.list[command] 79 | 80 | -- Don't run non-existent commands. 81 | if (not info) then 82 | return false 83 | end 84 | 85 | -- Check for server console access. 86 | if (not IsValid(client)) then 87 | return info.allowConsole == true 88 | end 89 | 90 | -- Delegate to the command's onCanRun. 91 | return info.onCanRun(client) 92 | end 93 | 94 | 95 | -- Converts a string into a list of arguments. 96 | function nut.command.parseArgs(message) 97 | -- All the arguments found. 98 | local arguments = {} 99 | 100 | -- The current argument being parsed. 101 | local argument 102 | 103 | -- Which character to skip to. 104 | local skip = 0 105 | 106 | -- Parse each character of the message. 107 | for i = 1, #message do 108 | -- Ignore this character if skipping. 109 | if (i < skip) then 110 | continue 111 | end 112 | 113 | local character = message[i] 114 | 115 | if (character:match("%s")) then 116 | if (not argument) then 117 | continue 118 | end 119 | 120 | -- Separate arguments by whitespaces. 121 | arguments[#arguments + 1] = argument 122 | argument = nil 123 | elseif (character == "'" or character == "\"") then 124 | -- Whether or not we are done looking for arguments. 125 | local finished 126 | 127 | -- Set the argument to be a string surrounded by quotes. 128 | local match = message:sub(i):match("%b"..character..character) 129 | 130 | -- Don't parse the match. 131 | if (not match) then 132 | -- If no ending quote was found, let the match be the rest 133 | -- of the string and set the finished state to true since 134 | -- there will be nothing left to parse. 135 | match = message:sub(i + 1) 136 | finished = true 137 | else 138 | skip = i + #match + 1 139 | match = match:sub(ARGS_START, -ARGS_START) 140 | end 141 | 142 | -- Add the match as an argument. 143 | arguments[#arguments + 1] = match 144 | 145 | if (finished) then 146 | break 147 | end 148 | else 149 | -- This character is not special so just build up an argument. 150 | argument = (argument or "")..character 151 | end 152 | end 153 | 154 | -- Add the leftover argument if it exists. 155 | if (argument) then 156 | arguments[#arguments + 1] = argument 157 | end 158 | 159 | return arguments 160 | end 161 | 162 | if (SERVER) then 163 | util.AddNetworkString("nutCommand") 164 | 165 | -- Finds and runs a command from a given chat message. 166 | function nut.command.parse(speaker, message) 167 | -- Find the command from the message. 168 | local start, finish, name = message:find(COMMAND_PREFIX.. 169 | COMMAND_PATTERN) 170 | 171 | -- Check if the command was found at the beginning. 172 | if (start == 1 and name) then 173 | local command = nut.command.list[name] 174 | 175 | -- Check if there is information about the command. 176 | if (not command) then 177 | return false 178 | end 179 | 180 | -- Parse the arguments. 181 | local argumentString = string.TrimLeft(message:sub(#name + 182 | ARGS_START)) 183 | local arguments = nut.command.parseArgs(argumentString) 184 | 185 | -- Run the command. 186 | local status, reason = command.onRun(speaker, arguments) 187 | reason = reason or "" 188 | 189 | if (status == false) then 190 | hook.Run("CommandFailed", speaker, name, arguments, reason) 191 | else 192 | hook.Run("CommandRan", speaker, name, arguments) 193 | end 194 | 195 | hook.Run("CommandParsed", speaker, message) 196 | 197 | return true 198 | end 199 | end 200 | 201 | -- Runs a command for a player. 202 | function nut.command.run(client, command, ...) 203 | assert(type(command) == "string", "command is not a string") 204 | 205 | -- Check if the player can run the command. 206 | if (nut.command.canRun(client, command) == false) then 207 | return false, "not allowed" 208 | end 209 | 210 | local info = nut.command.list[command] 211 | 212 | -- Get the arguments from the varargs. 213 | local arguments = {} 214 | 215 | for _, argument in ipairs({...}) do 216 | arguments[#arguments + 1] = tostring(argument) 217 | end 218 | 219 | -- Run the command if it exists. 220 | if (info) then 221 | local status, reason = info.onRun(client, arguments) 222 | 223 | if (type(reason) ~= "string") then 224 | reason = "" 225 | end 226 | 227 | if (status == nil) then 228 | status = true 229 | end 230 | 231 | if (status == false) then 232 | hook.Run("CommandFailed", client, command, arguments, reason) 233 | else 234 | hook.Run("CommandRan", client, command, arguments) 235 | end 236 | 237 | return status, reason 238 | end 239 | 240 | return false, "invalid command" 241 | end 242 | 243 | -- Receive commands from the client. 244 | net.Receive("nutCommand", function(length, client) 245 | -- Have a cooldown for running commands. 246 | if ((client.nutNextCommand or 0) < CurTime()) then 247 | client.nutNextCommand = CurTime() + COMMAND_COOLDOWN 248 | else 249 | return 250 | end 251 | 252 | -- Get the arguments from the client. 253 | local arguments = {} 254 | local command = net.ReadString() 255 | local count = net.ReadUInt(8) 256 | 257 | for i = 1, count do 258 | arguments[#arguments + 1] = net.ReadString() 259 | end 260 | 261 | -- Run the command. 262 | nut.command.run(client, command, unpack(arguments)) 263 | end) 264 | 265 | -- Set up a console command for the registered commands. 266 | hook.Add("PluginInitialized", "nutConsoleCommand", function() 267 | concommand.Add(nut.command.prefix, function(client, _, arguments) 268 | if (not arguments[1]) then 269 | return 270 | end 271 | 272 | nut.command.run(client, arguments[1], 273 | unpack(arguments, ARGS_START)) 274 | end) 275 | end) 276 | 277 | -- Log which commands are ran. 278 | hook.Add("CommandParsed", "nutCommandLog", function(client, message) 279 | if (not IsValid(client)) then 280 | return 281 | end 282 | 283 | ServerLog(client:Name().." ran '"..message.."'\n") 284 | end) 285 | else 286 | -- Run a command for the local player. 287 | function nut.command.run(command, ...) 288 | local arguments = {...} 289 | 290 | net.Start("nutCommand") 291 | net.WriteString(command) 292 | net.WriteUInt(#arguments, 8) 293 | 294 | for i = 1, #arguments do 295 | net.WriteString(tostring(arguments[i])) 296 | end 297 | net.SendToServer() 298 | end 299 | end 300 | -------------------------------------------------------------------------------- /gamemode/libraries/sh_currency.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sh_currency.lua 3 | Purpose: Creates a system for storing information about the gamemode's main 4 | currency. 5 | --]] 6 | 7 | -- The money entity to use. 8 | 9 | nut.currency = {} 10 | nut.currency.plural = "dollars" 11 | nut.currency.singular = "dollar" 12 | nut.currency.symbol = "$" 13 | nut.currency.entity = "nut_money" 14 | 15 | -- Sets the currency for the gamemode. 16 | function nut.currency.set(symbol, singular, plural) 17 | assert(type(symbol) == "string" or type(singular) == "string", 18 | "either symbol or singular needs to be given") 19 | 20 | nut.currency.symbol = symbol or "" 21 | nut.currency.singular = singular or "" 22 | nut.currency.plural = plural or singular 23 | 24 | -- Overwrite the toString to get the correct string format. 25 | if (symbol) then 26 | -- Set the toString to only use a symbol. 27 | function nut.currency.toString(amount) 28 | return symbol..amount 29 | end 30 | else 31 | -- If a symbol is not given, use the word form. 32 | if (not plural) then 33 | plural = singular 34 | end 35 | 36 | function nut.currency.toString(amount) 37 | return amount.." "..(amount == 1 and singular or plural) 38 | end 39 | end 40 | end 41 | 42 | -- Returns the string version of a given amount of money. 43 | function nut.currency.toString(value) 44 | return value 45 | end 46 | 47 | if (SERVER) then 48 | -- Spawns a money entity containing the given amount. 49 | function nut.currency.spawn(position, amount, setAmountFuncName) 50 | assert(type(position) == "Vector", "position is not a vector") 51 | assert(type(amount) == "number", "amount is not a number") 52 | 53 | if (type(setAmountFuncName) ~= "string") then 54 | setAmountFuncName = "SetAmount" 55 | end 56 | 57 | local entity = ents.Create(nut.currency.entity) 58 | entity:SetPos(position) 59 | entity:Spawn() 60 | entity[setAmountFuncName](entity, amount) 61 | 62 | hook.Run("MoneySpawned", entity) 63 | 64 | return entity 65 | end 66 | end 67 | 68 | -- Add the character extensions here. 69 | if (not nut.meta.character) then 70 | nut.util.include("nutscript2/gamemode/classes/sh_character.lua") 71 | end 72 | 73 | local CHARACTER = nut.meta.character 74 | 75 | -- Gives the character a certain amount of money. 76 | function CHARACTER:giveMoney(amount) 77 | assert(type(amount) == "number", "amount is not a number") 78 | 79 | self:setMoney(self:getMoney() + amount) 80 | end 81 | 82 | -- Takes a certain amount of money away from a character. 83 | function CHARACTER:takeMoney(amount) 84 | self:setMoney(self:getMoney() - amount) 85 | end 86 | 87 | -- Returns whether or not a player has the specified amount of money. 88 | function CHARACTER:hasMoney(amount) 89 | assert(type(amount) == "number", "amount is not a number") 90 | 91 | return self:getMoney() >= amount 92 | end -------------------------------------------------------------------------------- /gamemode/libraries/sh_data.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sh_data.lua 3 | Purpose: Creates functions for making it easier to store abitrary and persistent 4 | data. 5 | --]] 6 | 7 | -- The character after the curly brace. 8 | local ENCODED_START = 2 9 | 10 | -- The character before the closing curly brace. 11 | local ENCODED_END = -2 12 | 13 | nut.data = nut.data or {} 14 | nut.data.stored = nut.data.stored or {} 15 | nut.data.global = nut.data.global or {} 16 | 17 | -- Sets a persistent data value. 18 | function nut.data.set(key, value, ignoreMap, global) 19 | -- Find where the data should be stored. 20 | local store = global and nut.data.global or nut.data.stored 21 | local path = "nutscript2/" 22 | 23 | if (not global) then 24 | path = path..engine.ActiveGamemode().."/" 25 | end 26 | 27 | if (ignoreMap) then 28 | store["*"] = store["*"] or {} 29 | store = store["*"] 30 | else 31 | local map = game.GetMap() 32 | 33 | store[map] = store[map] or {} 34 | store = store[map] 35 | 36 | path = path..map.."/" 37 | end 38 | 39 | -- Store the old value for the DataSet hook. 40 | local oldValue = store[key] 41 | 42 | -- Set the data value. 43 | store[key] = value 44 | 45 | -- Write the value to disk. 46 | path = path..key..".txt" 47 | 48 | if (value == nil) then 49 | file.Delete(path) 50 | else 51 | local encoded = pon.encode({value}) 52 | encoded = encoded:sub(ENCODED_START, ENCODED_END) 53 | 54 | file.Write(path, encoded) 55 | end 56 | 57 | hook.Run("DataSet", key, oldValue, value, ignoreMap, global) 58 | end 59 | 60 | -- Reads a NutScript data value. 61 | function nut.data.get(key, default, ignoreMap, global, refresh) 62 | -- Find where the data should be stored. 63 | local store = global and nut.data.global or nut.data.stored 64 | local path = "nutscript2/" 65 | 66 | if (not global) then 67 | path = path..engine.ActiveGamemode().."/" 68 | end 69 | 70 | if (ignoreMap) then 71 | store["*"] = store["*"] or {} 72 | store = store["*"] 73 | else 74 | local map = game.GetMap() 75 | 76 | store[map] = store[map] or {} 77 | store = store[map] 78 | 79 | path = path..map.."/" 80 | end 81 | 82 | -- Try reading the data from memory. 83 | if (not refresh and store[key] ~= nil) then 84 | return store[key] 85 | end 86 | 87 | -- Get the file containing the data. 88 | path = path..key..".txt" 89 | 90 | -- Read the value from disk. 91 | local encoded = file.Read(path, "DATA") 92 | 93 | -- Decode the encoded data. 94 | if (encoded) then 95 | local status, result = pcall(pon.decode, "{"..encoded.."}") 96 | 97 | if (status) then 98 | result = result[1] 99 | 100 | -- Store the decoded value in memory. 101 | store[key] = result 102 | 103 | return result 104 | end 105 | end 106 | 107 | return default 108 | end 109 | 110 | -- Set up the folder structure for NutScript data. 111 | hook.Add("Initialize", "nutDataStore", function() 112 | local name = engine.ActiveGamemode():lower() 113 | 114 | file.CreateDir("nutscript2") 115 | file.CreateDir("nutscript2/"..name) 116 | file.CreateDir("nutscript2/"..name.."/"..game.GetMap()) 117 | end) 118 | -------------------------------------------------------------------------------- /gamemode/libraries/sh_item.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sh_item.lua 3 | Purpose: Sets up a system so items and inventories can be used to manage 4 | things which characters can interact with. 5 | --]] 6 | 7 | -- Where the file extension starts. 8 | local EXTENSION = -5 9 | 10 | nut.item = nut.item or {} 11 | nut.item.list = nut.item.list or {} 12 | nut.item.abstract = nut.item.abstract or {} 13 | nut.item.inventories = nut.item.inventories or {} 14 | 15 | -- Loads an item from a given file. 16 | function nut.item.load(path, parentItem, isAbstract) 17 | assert(type(path) == "string", "path is not a string") 18 | 19 | -- Create a string identifier for the item. 20 | local id = string.GetFileFromFilename(path) 21 | id = id:sub(1, EXTENSION) 22 | id = id:gsub("sh_", "") 23 | 24 | -- Set up a global table so the file can store item information. 25 | ITEM = {id = id} 26 | 27 | -- If a parent ID is given, derive the item from the parent. 28 | if (parentItem) then 29 | local parent = nut.item.abstract[parentItem] or 30 | nut.item.list[parentItem] 31 | 32 | if (parent) then 33 | table.Inherit(ITEM, parent) 34 | else 35 | ErrorNohalt("Parent '"..parentItem.."' does not exist for".. 36 | " item ("..path..")") 37 | end 38 | end 39 | 40 | -- Include and register the item. 41 | nut.util.include(path, "shared") 42 | nut.item.register(ITEM) 43 | 44 | ITEM = nil 45 | end 46 | 47 | -- Loads all the items within a given directory. 48 | function nut.item.loadFromDir(path) 49 | assert(type(path) == "string", "path is not a string") 50 | 51 | -- Look in the items directory within path. 52 | path = path.."/items/" 53 | 54 | -- Load all the base items first as abstract items. 55 | local _, baseItems = file.Find(path.."base/*.lua", "LUA") 56 | 57 | for _, basePath in ipairs(baseItems) do 58 | nut.item.load(path.."base/"..basePath, nil, true) 59 | end 60 | 61 | -- Find all the items within the path. 62 | local files, folders = file.Find(path.."*", "LUA") 63 | 64 | -- Load all the files as regular items. 65 | for _, itemPath in ipairs(files) do 66 | nut.item.load(path..itemPath) 67 | end 68 | 69 | -- Load all the folders as items that derive from a parent. 70 | -- The folder's name should match the parent's identifier. 71 | for _, folderPath in ipairs(folders) do 72 | -- Ignore the base folder since we already checked it. 73 | if (folderPath == "base") then 74 | continue 75 | end 76 | 77 | local files = file.Find(path..folderPath.."/*.lua", "LUA") 78 | 79 | for _, itemPath in ipairs(files) do 80 | nut.item.load(path..folderPath.."/"..itemPath, folderPath) 81 | end 82 | end 83 | end 84 | 85 | -- Sets up an item so it can be used. 86 | function nut.item.register(item) 87 | assert(type(item) == "table", "item is not a table") 88 | 89 | -- Store the item in its correct list. 90 | if (ITEM.isAbstract or isAbstract) then 91 | nut.item.abstract[ITEM.id] = ITEM 92 | else 93 | nut.item.list[ITEM.id] = ITEM 94 | end 95 | 96 | -- Notify the item that it was registered. 97 | if (type(ITEM.onRegistered) == "function") then 98 | ITEM:onRegistered() 99 | end 100 | 101 | -- Notify the parent that it was registered. 102 | if (ITEM.BaseClass and 103 | type(ITEM.BaseClass.onChildRegistered) == "function") then 104 | ITEM.BaseClass:onChildRegistered(item) 105 | end 106 | 107 | hook.Run("ItemRegistered", item) 108 | end 109 | 110 | -- Instances a new inventory object. 111 | function nut.item.newInv(id, owner) 112 | -- Provide default values for the id and owner. 113 | if (type(id) ~= "number") then 114 | id = 0 115 | end 116 | 117 | if (type(owner) ~= "number") then 118 | owner = 0 119 | end 120 | 121 | -- Create a new inventory metatable. 122 | local inventory = setmetatable({id = id, owner = owner}, nut.meta.inventory) 123 | nut.item.inventories[id] = inventory 124 | 125 | -- Adjust the instance if needed. 126 | hook.Run("InventoryInstanced", inventory) 127 | 128 | return inventory 129 | end 130 | 131 | -- Serverside portion for inventories. 132 | if (CLIENT) then return end 133 | 134 | -- Creates a new inventory that is stored in the database. 135 | function nut.item.createInv(owner, callback) 136 | assert(type(owner) == "number", "owner is not a number") 137 | 138 | -- Insert the inventory into the database. 139 | nut.db.insert(INVENTORIES, {ownerID = owner}, function(results) 140 | if (results) then 141 | -- Get the unique ID for the inventory. 142 | local id = nut.db.getInsertID() 143 | 144 | -- Create an inventory object. 145 | local inventory = nut.item.newInv(id, owner) 146 | hook.Run("InventoryCreated", inventory) 147 | 148 | -- Run the callback with the inventory. 149 | if (type(callback) == "function") then 150 | callback(inventory) 151 | end 152 | end 153 | end) 154 | end 155 | 156 | -- Loads an inventory from the database into an object. 157 | function nut.item.restoreInv(id, callback) 158 | assert(type(id) == "number", "id is not a number") 159 | assert(id >= 0, "id can not be negative") 160 | 161 | -- Select the inventory corresponding to the given ID 162 | -- from the database. 163 | nut.db.select(INVENTORIES, {"ownerID"}, "invID = "..id, function(results) 164 | if (results and results[1]) then 165 | results = results[1] 166 | 167 | -- Create an inventory object using the results. 168 | local inventory = nut.item.newInv(id, 169 | tonumber(results.ownerID) or 0) 170 | hook.Run("InventoryRestored", inventory) 171 | 172 | -- Load the inventory's items. 173 | inventory:load(function() 174 | hook.Run("InventoryLoaded", inventory) 175 | 176 | -- Run the callback passing the inventory. 177 | if (type(callback) == "function") then 178 | callback(inventory) 179 | end 180 | end) 181 | elseif (type(callback) == "function") then 182 | callback() 183 | end 184 | end) 185 | end 186 | 187 | -- Loads inventories which belongs to a given owner. 188 | function nut.item.restoreInvFromOwner(owner, callback, limit) 189 | assert(type(owner) == "number", "owner must be a number") 190 | assert(owner >= 0, "owner can not be negative") 191 | 192 | -- Select the inventories that belong to the given owner. 193 | nut.db.select(INVENTORIES, {"invID"}, "ownerID = "..owner, 194 | function(results) 195 | if (results and #results > 0) then 196 | -- Convert each result into an inventory object. 197 | for index, result in ipairs(results) do 198 | -- Create an inventory object using the results. 199 | local inventory = nut.item.newInv(tonumber(result.invID) or 0, 200 | owner) 201 | 202 | -- Store the inventory. 203 | nut.item.inventories[inventory.id] = inventory 204 | 205 | hook.Run("InventoryRestored", inventory, index) 206 | 207 | -- Load the inventory's items. 208 | inventory:load(function() 209 | hook.Run("InventoryLoaded", inventory, index) 210 | 211 | -- Run the callback with the loaded inventory. 212 | if (type(callback) == "function") then 213 | callback(inventory, index) 214 | end 215 | end) 216 | end 217 | elseif (type(callback) == "function") then 218 | callback() 219 | end 220 | end, limit, "invID") 221 | end 222 | 223 | hook.Add("DatabaseConnected", "nutInventoryTable", function() 224 | local MYSQL_CREATE = [[ 225 | CREATE TABLE IF NOT EXISTS `%s` ( 226 | `invID` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 227 | `ownerID` INT(11) UNSIGNED NOT NULL 228 | ) AUTO_INCREMENT=1; 229 | ]] 230 | 231 | local SQLITE_CREATE = [[ 232 | CREATE TABLE IF NOT EXISTS `%s` ( 233 | `invID` INTEGER PRIMARY KEY NOT NULL, 234 | `ownerID` INTEGER NOT NULL 235 | ); 236 | ]] 237 | 238 | -- The table name for the inventories. 239 | INVENTORIES = engine.ActiveGamemode():lower().."_inventories" 240 | 241 | -- Create the inventories table. 242 | if (nut.db.sqlModule == nut.db.modules.sqlite) then 243 | nut.db.query(SQLITE_CREATE:format(INVENTORIES)) 244 | else 245 | nut.db.query(MYSQL_CREATE:format(INVENTORIES)) 246 | end 247 | end) -------------------------------------------------------------------------------- /gamemode/libraries/sh_plugin.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: sh_plugin.lua 3 | Purpose: Creates the plugin library which allows easier modification to 4 | the framework and gamemodes with drag and drop plugins. 5 | --]] 6 | 7 | -- Cache the plugin hooks. 8 | HOOK_CACHE = {} 9 | 10 | -- Where the file extension starts in a path. 11 | local EXT_START = -4 12 | 13 | nut.plugin = nut.plugin or {} 14 | nut.plugins = nut.plugins or {} 15 | 16 | -- A list of plugins that have been disabled. 17 | nut.plugin.disabled = nut.plugin.disabled or {} 18 | 19 | -- A list of things that are loaded with plugins. 20 | nut.plugin.components = nut.plugin.components or {} 21 | 22 | -- Adds a plugin component which allows for extra features to be loaded in. 23 | function nut.plugin.addComponent(name, info) 24 | assert(type(name) == "string", "name is not a string") 25 | assert(type(info) == "table", "info is not a table") 26 | 27 | nut.plugin.components[name] = info 28 | end 29 | 30 | -- Includes plugins within a given directory. 31 | local function includePluginDir(base) 32 | local files, folders = file.Find(base.."/plugins/*", "LUA") 33 | 34 | for _, path in ipairs(files) do 35 | nut.plugin.load(path:sub(1, EXT_START - 1), base.."/plugins/"..path) 36 | end 37 | 38 | for _, path in ipairs(folders) do 39 | nut.plugin.load(path, base.."/plugins/"..path) 40 | end 41 | end 42 | 43 | -- Loads all available plugins. 44 | function nut.plugin.initialize() 45 | includePluginDir("nutscript2") 46 | includePluginDir(engine.ActiveGamemode()) 47 | includePluginDir("ns_plugins") 48 | 49 | -- Load components for the gamemode. 50 | nut.plugin.loadComponents(GAMEMODE, engine.ActiveGamemode().."/gamemode") 51 | 52 | for name, component in pairs(nut.plugin.components) do 53 | if (type(component.onLoaded) == "function") then 54 | component.onLoaded(GAMEMODE) 55 | end 56 | end 57 | 58 | hook.Run("PluginInitialized") 59 | end 60 | 61 | -- Loads a plugin from a given path. 62 | function nut.plugin.load(id, path, name) 63 | name = name or "plugin" 64 | 65 | assert(type(id) == "string", "id is not a string") 66 | assert(type(path) == "string", "path is not a string") 67 | assert(type(name) == "string", "name is not a string") 68 | 69 | if (hook.Run("PluginShouldLoad", id) == false) then 70 | return false 71 | end 72 | 73 | -- Create a table to store plugin information. 74 | local plugin = nut.plugins[id] or {id = id, path = path} 75 | local usingFile 76 | 77 | -- Make this table globally accessible. 78 | _G[name:upper()] = plugin 79 | 80 | -- Check if we are including a single file or a folder. 81 | if (path:sub(EXT_START) == ".lua") then 82 | nut.util.include(path, "shared") 83 | usingFile = true 84 | else 85 | assert(file.Exists(path.."/sh_"..name..".lua", "LUA"), 86 | id.." is missing sh_plugin.lua!") 87 | 88 | nut.util.include(path.."/sh_"..name..".lua") 89 | nut.plugin.loadComponents(plugin) 90 | end 91 | 92 | -- Register the plugin so it works. 93 | nut.plugin.register(plugin) 94 | hook.Run("PluginLoaded", plugin) 95 | 96 | -- Call the component's onLoaded. 97 | if (not usingFile) then 98 | for name, component in pairs(nut.plugin.components) do 99 | if (type(component.onLoaded) == "function") then 100 | component.onLoaded(plugin) 101 | end 102 | end 103 | end 104 | 105 | _G[name:upper()] = nil 106 | 107 | return true 108 | end 109 | 110 | -- Loads components of a plugin from a given path. 111 | function nut.plugin.loadComponents(plugin, path) 112 | assert(type(plugin) == "table", "plugin is not a table") 113 | 114 | -- Include any plugin components. 115 | for name, component in pairs(nut.plugin.components) do 116 | if (type(component.onInclude) == "function" and 117 | hook.Run("PluginComponentShouldLoad", plugin, name) ~= false) then 118 | component.onInclude(plugin, path or plugin.path) 119 | end 120 | end 121 | 122 | hook.Run("PluginLoadComponents", plugin) 123 | end 124 | 125 | -- Sets up a plugin so it can be used by the framework. 126 | function nut.plugin.register(plugin) 127 | assert(type(plugin) == "table", "plugin is not a table") 128 | assert(plugin.id, "plugin does not have an identifier") 129 | 130 | -- Notify the plugin that it is being registered. 131 | if (type(plugin.onRegister) == "function") then 132 | plugin:onRegister() 133 | end 134 | 135 | -- Add the plugin hooks to the list of plugin hooks. 136 | for name, callback in pairs(plugin) do 137 | if (type(callback) == "function") then 138 | HOOK_CACHE[name] = HOOK_CACHE[name] or {} 139 | HOOK_CACHE[name][plugin] = callback 140 | end 141 | end 142 | 143 | hook.Run("PluginRegistered", plugin) 144 | 145 | -- Add the plugin to the list of plugins. 146 | nut.plugins[tostring(plugin.id)] = plugin 147 | end 148 | 149 | -- Allow plugins to load their own plugins. 150 | nut.plugin.addComponent("plugins", { 151 | onInclude = function(plugin, path) 152 | includePluginDir(path) 153 | end 154 | }) 155 | 156 | -- Overwrite hook.Call so plugin hooks run. 157 | hook.NutCall = hook.NutCall or hook.Call 158 | 159 | function hook.Call(name, gm, ...) 160 | local hooks = HOOK_CACHE[name] 161 | 162 | -- Run the plugin hooks from the cache. 163 | if (hooks) then 164 | -- Possible return values, assuming there are no more than 165 | -- six return values. 166 | local a, b, c, d, e, f 167 | 168 | -- Run all the plugin hooks. 169 | for plugin, callback in pairs(hooks) do 170 | a, b, c, d, e, f = callback(plugin, ...) 171 | 172 | -- If a hook returned value(s), return it. 173 | if (a ~= nil) then 174 | return a, b, c, d, e, f 175 | end 176 | end 177 | end 178 | 179 | -- Otherwise, do the normal hook calls. 180 | return hook.NutCall(name, gm, ...) 181 | end 182 | 183 | nut.util.include("nutscript2/gamemode/core/sh_components.lua") -------------------------------------------------------------------------------- /gamemode/libraries/sh_team.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: sh_team.lua 3 | Purpose: Provides an easier interface for creating and managing teams. 4 | --]] 5 | 6 | nut.team = {} 7 | nut.team.list = {} 8 | nut.team.classes = {} 9 | 10 | -- Loads a team from a path to a file. 11 | function nut.team.load(path) 12 | -- Create a table to store team information. 13 | TEAM = {} 14 | 15 | -- Include the team's file. 16 | nut.util.include(path, "shared") 17 | 18 | assert(type(TEAM.id) == "number", "team ID not set ("..path..")") 19 | assert(type(TEAM.name) == "string", "team is missing name ("..path..")") 20 | assert(type(TEAM.color) == "table", "team is missing color ("..path..")") 21 | 22 | -- Register the team. 23 | team.SetUp(TEAM.id, TEAM.name, TEAM.color) 24 | nut.team.list[TEAM.id] = TEAM 25 | 26 | TEAM = nil 27 | end 28 | 29 | -- Loads a class from a path to a file. 30 | function nut.team.loadClass(path) 31 | -- Create a table to store class information. 32 | CLASS = {} 33 | 34 | -- Include the class's file. 35 | nut.util.include(path, "shared") 36 | 37 | assert(type(CLASS.id) == "number", "class ID is not a number") 38 | assert(type(CLASS.name) == "string", "class name is not a string") 39 | assert(type(CLASS.team) == "number", "class team is not a number".. 40 | " ("..path..")") 41 | assert(team.Valid(CLASS.team), "class team is not a valid team".. 42 | " ("..path..")") 43 | 44 | -- Register the class. 45 | nut.team.classes[CLASS.id] = CLASS 46 | 47 | CLASS = nil 48 | end 49 | 50 | -- Loads classes and teams from a given directory. 51 | function nut.team.loadFromDir(base) 52 | for _, path in ipairs(file.Find(base.."/teams/*.lua", "LUA")) do 53 | nut.team.load(base.."/teams/"..path) 54 | end 55 | 56 | for _, path in ipairs(file.Find(base.."/classes/*.lua", "LUA")) do 57 | nut.team.loadClass(base.."/classes/"..path) 58 | end 59 | end 60 | 61 | if (SERVER) then 62 | -- Sets up the loadout for a team member. 63 | function nut.team.loadout(client, teamID) 64 | assert(type(client) == "Player", "client is not a player") 65 | assert(IsValid(client), "client is not a valid player") 66 | 67 | -- Get information about the team that the player is in. 68 | local info = nut.team.list[client:Team()] 69 | 70 | if (not info) then 71 | return false 72 | end 73 | 74 | -- Get information about the class that the player is in. 75 | local character = client:getChar() 76 | 77 | if (character) then 78 | local classInfo = nut.team.classes[character:getClass()] 79 | 80 | -- Merge the class variables with the team variables. 81 | if (classInfo and classInfo.team == teamID) then 82 | info = table.Copy(info) 83 | table.Merge(info, classInfo) 84 | end 85 | end 86 | 87 | -- Give weapons from the loadout. 88 | if (type(info.loadout) == "table") then 89 | for _, item in pairs(info.loadout) do 90 | client:Give(item) 91 | end 92 | end 93 | 94 | -- Set the various team parameters. 95 | client:SetHealth(info.health or client:Health()) 96 | client:SetMaxHealth(info.maxHealth or client:GetMaxHealth()) 97 | client:SetArmor(info.armor or client:Armor()) 98 | client:SetRunSpeed(info.runSpeed or client:GetRunSpeed()) 99 | client:SetWalkSpeed(info.walkSpeed or client:GetWalkSpeed()) 100 | 101 | -- Run the onLoadout callback. 102 | if (type(info.onLoadout) == "function") then 103 | info:onLoadout(client) 104 | end 105 | 106 | return true 107 | end 108 | 109 | -- Implement the team loadout. 110 | hook.Add("PlayerLoadout", "nutTeamLoadout", function(client) 111 | nut.team.loadout(client, client:Team()) 112 | end) 113 | end -------------------------------------------------------------------------------- /gamemode/libraries/sv_character.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sv_database.lua 3 | Purpose: Creates the functions responsible for managing characters between 4 | the server and the database. 5 | --]] 6 | 7 | if (not nut.char) then 8 | nut.util.include("sh_character.lua") 9 | end 10 | 11 | if (not nut.db) then 12 | nut.util.include("sv_database.lua") 13 | end 14 | 15 | util.AddNetworkString("nutCharData") 16 | util.AddNetworkString("nutCharDelete") 17 | util.AddNetworkString("nutCharTempVar") 18 | util.AddNetworkString("nutCharVar") 19 | 20 | -- How many bits are in a long integer. 21 | local LONG = 32 22 | 23 | -- Inserts and instances a character. 24 | function nut.char.create(info, callback, context) 25 | assert(type(info) == "table", "info is not a table") 26 | 27 | context = context or {} 28 | 29 | -- Check if the player can create a character. 30 | local fault = hook.Run("CharacterPreCreate", info, context) 31 | 32 | if (type(fault) == "string") then 33 | return false, fault 34 | end 35 | 36 | -- Allow modifications to the given info. 37 | hook.Run("CharacterAdjustInfo", info, context) 38 | 39 | -- Make sure there are no extraneous variables. 40 | for key, value in pairs(info) do 41 | if (key ~= "steamID" and not nut.char.vars[key]) then 42 | return false, "invalid variable ("..key..")" 43 | end 44 | end 45 | 46 | -- Insert the character into the database. 47 | nut.char.insert(info, function(id) 48 | local character 49 | 50 | if (id) then 51 | -- The value of a character variable. 52 | local value 53 | 54 | -- If the character was made, make an object for it. 55 | character = nut.char.new(id) 56 | 57 | -- Copy the given info to the character object. 58 | for name, variable in pairs(nut.char.vars) do 59 | if (info[variable.field] ~= nil) then 60 | character.vars[name] = info[variable.field] 61 | elseif (info[name] ~= nil) then 62 | character.vars[name] = info[name] 63 | end 64 | 65 | -- Notify the variable that a character was created if needed. 66 | if (type(variable).onCreate == "function") then 67 | variable.onCreate(character) 68 | end 69 | end 70 | 71 | hook.Run("CharacterCreated", character, context) 72 | end 73 | 74 | if (type(callback) == "function") then 75 | callback(character) 76 | end 77 | end) 78 | 79 | return true 80 | end 81 | 82 | -- Inserts a character into the database. 83 | function nut.char.insert(info, callback) 84 | assert(type(info) == "table", "info is not a table") 85 | 86 | -- Create a table to store what values will be inserted. 87 | local data = {} 88 | 89 | -- Get the data to insert into the database. 90 | for name, variable in pairs(nut.char.vars) do 91 | if (variable.field and not variable.isConstant) then 92 | local value = info[name] or variable.onGetDefault() 93 | 94 | if (variable.notNull and value == nil) then 95 | error(name.." can not be null") 96 | end 97 | 98 | data[variable.field] = value 99 | end 100 | end 101 | 102 | -- Add some creation information. 103 | data.steamID = info.owner or info.steamID or 0 104 | data.createTime = os.time() 105 | data.lastJoin = data.createTime 106 | 107 | -- Insert the data into the database. 108 | nut.db.insert(CHARACTERS, data, function(result) 109 | -- Run the callback with the resulting data. 110 | if (result and type(callback) == "function") then 111 | callback(nut.db.getInsertID()) 112 | elseif (type(callback) == "function") then 113 | callback() 114 | end 115 | end) 116 | end 117 | 118 | -- Loads a character from the database into an instance. 119 | function nut.char.load(id, callback, reload) 120 | assert(type(id) == "number", "id is not a number") 121 | assert(id >= 0, "id can not be negative") 122 | 123 | -- Don't load the character if it already exists. 124 | if (not reload and nut.char.list[id]) then 125 | if (type(callback) == "function") then 126 | callback(nut.char.list[id]) 127 | end 128 | 129 | return 130 | end 131 | 132 | -- The character that contains the results of loading. 133 | local character 134 | 135 | -- Get the fields that are needed to load the character. 136 | local fields = {} 137 | 138 | for name, variable in pairs(nut.char.vars) do 139 | if (variable.field) then 140 | fields[#fields + 1] = variable.field 141 | end 142 | end 143 | 144 | -- Load the data from the database. 145 | nut.db.select(CHARACTERS, fields, "id = "..id, function(result) 146 | if (result and result[1]) then 147 | result = result[1] 148 | 149 | -- Create a character object to store the results. 150 | character = nut.char.new(id) 151 | 152 | -- Load variables from the results. 153 | for name, variable in pairs(nut.char.vars) do 154 | local field = variable.field 155 | local value = result[field] 156 | 157 | -- Allow for custom loading of this variable. 158 | if (type(variable.onLoad) == "function") then 159 | variable.onLoad(character) 160 | 161 | continue 162 | end 163 | 164 | -- Get the default value of the variable. 165 | local default = variable.onGetDefault() 166 | 167 | -- Convert the string value to the correct Lua type. 168 | if (default and field and value) then 169 | -- Get the suggested type. 170 | local defaultType = type(default) 171 | 172 | -- Convert to the suggested type if applicable. 173 | if (ENCODE_TYPES[defaultType]) then 174 | local status, result = pcall(pon.decode, value) 175 | 176 | if (status) then 177 | value = result[1] 178 | else 179 | ErrorNoHalt("Failed to decode "..name.." for ".. 180 | "character #"..id..".\n") 181 | end 182 | elseif (defaultType == "number") then 183 | value = tonumber(value) 184 | elseif (defaultType == "boolean") then 185 | value = tobool(value) 186 | end 187 | end 188 | 189 | -- Store the retrieved value. 190 | if (field) then 191 | character.vars[name] = value 192 | end 193 | end 194 | 195 | else 196 | ErrorNoHalt("Failed to load character #"..id.."\n".. 197 | nut.db.lastError.."\n") 198 | end 199 | 200 | hook.Run("CharacterLoaded", character) 201 | 202 | -- Run the callback if one is given. 203 | if (type(callback) == "function") then 204 | callback(character) 205 | end 206 | end, 1) 207 | end 208 | 209 | -- The queries needed to create tables for NutScript characters. 210 | local MYSQL_CHARACTER = [[ 211 | CREATE TABLE IF NOT EXISTS `%s` ( 212 | `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, 213 | `name` VARCHAR(70) NOT NULL, 214 | `desc` TEXT, 215 | `model` VARCHAR(160), 216 | `createTime` INT(11) UNSIGNED NOT NULL, 217 | `lastJoin` INT(11) UNSIGNED NOT NULL DEFAULT 0, 218 | `money` INT(11) UNSIGNED NOT NULL DEFAULT 0, 219 | `team` TINYINT(4) UNSIGNED, 220 | `steamID` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, 221 | PRIMARY KEY (`id`) 222 | ) AUTO_INCREMENT=1; 223 | ]] 224 | 225 | local MYSQL_CHAR_DATA = [[ 226 | CREATE TABLE IF NOT EXISTS `%s` ( 227 | `id` INT UNSIGNED NOT NULL, 228 | `key` VARCHAR(65) NOT NULL, 229 | `value` VARCHAR(255) NOT NULL, 230 | PRIMARY KEY (`id`, `key`) 231 | ); 232 | ]] 233 | 234 | local SQLITE_CHARACTER = [[ 235 | CREATE TABLE IF NOT EXISTS `%s` ( 236 | id INTEGER PRIMARY KEY, 237 | name TEXT, 238 | desc TEXT, 239 | model TEXT, 240 | createTime UNSIGNED INTEGER, 241 | lastJoin UNSIGNED INTEGER, 242 | money UNSIGNED INTEGER, 243 | team UNSIGNED INTEGER, 244 | steamID UNSIGNED INTEGER 245 | ); 246 | ]] 247 | 248 | local SQLITE_CHAR_DATA = [[ 249 | CREATE TABLE IF NOT EXISTS `%s` ( 250 | id UNSIGNED INTEGER, 251 | key TEXT, 252 | value TEXT, 253 | PRIMARY KEY (id, key) 254 | ); 255 | ]] 256 | 257 | -- Sets up the character table 258 | hook.Add("DatabaseConnected", "nutCharTableSetup", function() 259 | -- Set global variables for the character tables. 260 | CHARACTERS = engine.ActiveGamemode():lower().."_characters" 261 | CHAR_DATA = engine.ActiveGamemode():lower().."_chardata" 262 | 263 | -- Create the tables themselves. 264 | if (nut.db.sqlModule == nut.db.modules.sqlite) then 265 | nut.db.query(SQLITE_CHARACTER:format(CHARACTERS)) 266 | nut.db.query(SQLITE_CHAR_DATA:format(CHAR_DATA)) 267 | else 268 | nut.db.query(MYSQL_CHARACTER:format(CHARACTERS)) 269 | nut.db.query(MYSQL_CHAR_DATA:format(CHAR_DATA)) 270 | end 271 | end) 272 | -------------------------------------------------------------------------------- /gamemode/libraries/sv_database.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: libraries/sv_database.lua 3 | Purpose: Allows for easy access to a database so one can store and load 4 | information. 5 | --]] 6 | 7 | -- Types of values that can be encoded using pON. 8 | ENCODE_TYPES = {} 9 | ENCODE_TYPES["Angle"] = true 10 | ENCODE_TYPES["Vector"] = true 11 | ENCODE_TYPES["table"] = true 12 | 13 | -- The default MySQL port. 14 | local DEFAULT_PORT = 3306 15 | 16 | nut.db = nut.db or {} 17 | nut.db.lastError = nut.db.lastError or "" 18 | nut.db.modules = {} 19 | 20 | -- Include SQL module implementations. 21 | nut.util.includeDir("core/sql", "server") 22 | 23 | -- Connects to a database using the given module. 24 | function nut.db.connect(sqlModule, callback) 25 | assert(type(sqlModule) == "table", "sqlModule is not a table") 26 | assert(type(sqlModule.connect) == "function", 27 | "sqlModule is missing a connect function") 28 | assert(type(sqlModule.query) == "function", 29 | "sqlModule is missing a query function") 30 | assert(type(sqlModule.escape) == "function", 31 | "sqlModule is missing a escape function") 32 | assert(type(sqlModule.getInsertID) == "function", 33 | "sqlModule is missing a getInsertID function") 34 | 35 | -- Check to see connection details are provided, if needed. 36 | if (sqlModule.needsDetails) then 37 | assert(nut.db.hostname, "nut.db.hostname is not set") 38 | assert(nut.db.username, "nut.db.username is not set") 39 | assert(nut.db.password, "nut.db.password is not set") 40 | assert(nut.db.database, "nut.db.database is not set") 41 | 42 | nut.db.port = tonumber(nut.db.port) or DEFAULT_PORT 43 | end 44 | 45 | -- Set the nut.db functions to correspond to sqlModule's. 46 | nut.db.query = sqlModule.query 47 | nut.db.escape = sqlModule.escape 48 | nut.db.getInsertID = sqlModule.getInsertID 49 | nut.db.sqlModule = sqlModule 50 | 51 | -- Defer the connection until plugins have loaded. 52 | if (nut.loading) then 53 | nut.db.connectCallback = callback 54 | else 55 | sqlModule.connect(callback) 56 | end 57 | end 58 | 59 | -- Deletes rows within the database under given conditions. 60 | function nut.db.delete(tableName, condition, callback, limit) 61 | -- Start the deletion query. 62 | local query = "DELETE FROM "..tostring(tableName) 63 | 64 | -- Add a condition if one was given. 65 | if (condition) then 66 | query = query.." WHERE "..tostring(condition) 67 | end 68 | 69 | -- Add a limit if one was given. 70 | if (limit) then 71 | query = query.." LIMIT "..tostring(limit) 72 | end 73 | 74 | nut.db.query(query, callback) 75 | end 76 | 77 | -- Makes a string safer for queries. 78 | nut.db.escape = sql.SQLStr 79 | 80 | -- Returns the index of the last inserted row. 81 | function nut.db.getInsertID() 82 | error("nut.db.getInsertID has not been overwritten!") 83 | end 84 | 85 | -- Inserts given values into the given table. 86 | function nut.db.insert(tableName, data, callback) 87 | assert(type(data) == "table", "data is not a table") 88 | 89 | -- Get the start of the insert query. 90 | local query = "INSERT INTO "..tostring(tableName) 91 | 92 | -- Convert the table into columns and values. 93 | local columns = {} 94 | local values = {} 95 | 96 | for k, v in pairs(data) do 97 | columns[#columns + 1] = "`"..tostring(k).."`" 98 | values[#values + 1] = nut.db.toString(v) 99 | end 100 | 101 | -- Do nothing if the table is empty. 102 | if (#columns == 0) then 103 | return 104 | end 105 | 106 | -- Put the columns and values into the insert query. 107 | query = query.." ("..table.concat(columns, ",")..") VALUES (".. 108 | table.concat(values, ",")..")" 109 | 110 | -- Run the generated query. 111 | nut.db.query(query, callback) 112 | end 113 | 114 | -- Does a raw query to the database. 115 | function nut.db.query(query, callback) 116 | error("nut.db.query has not been overwritten!") 117 | end 118 | 119 | -- Selects given fields in a given table. 120 | function nut.db.select(tableName, fields, condition, callback, limit, order) 121 | -- Convert fields into a string list of fields. 122 | if (type(fields) == "table") then 123 | -- Do nothing if no fields are desired. 124 | if (#fields == 0) then 125 | return 126 | end 127 | 128 | -- Add backticks around the fields. 129 | for i = 1, #fields do 130 | fields[i] = "`"..fields[i].."`" 131 | end 132 | 133 | fields = table.concat(fields, ",") 134 | else 135 | fields = "*" 136 | end 137 | 138 | -- Start generating the query. 139 | local query = "SELECT "..fields.." FROM "..tostring(tableName) 140 | 141 | -- Add a where clause if needed. 142 | if (condition) then 143 | query = query.." WHERE "..tostring(condition) 144 | end 145 | 146 | -- Order the results if an order is given. 147 | if (order) then 148 | query = query.." ORDER BY "..tostring(order) 149 | end 150 | 151 | -- Add a limit if one is given. 152 | if (limit) then 153 | query = query.." LIMIT "..limit 154 | end 155 | 156 | nut.db.query(query, callback) 157 | end 158 | 159 | -- Converts a Lua value into an escaped string for the database. 160 | function nut.db.toString(value, noQuotes) 161 | -- The type of value. 162 | local valueType = type(value) 163 | 164 | -- Handle certain types of values. 165 | if (valueType == "boolean") then 166 | return value and "1" or "0" 167 | elseif (ENCODE_TYPES[valueType]) then 168 | local output = nut.db.escape(pon.encode({value})) 169 | 170 | if (not noQuotes) then 171 | output = "\""..output.."\"" 172 | end 173 | 174 | return output 175 | elseif (valueType == "string") then 176 | local output = nut.db.escape(value) 177 | 178 | if (not noQuotes) then 179 | output = "\""..output.."\"" 180 | end 181 | 182 | return output 183 | elseif (valueType == "nil" or valueType == "no value") then 184 | return "NULL" 185 | end 186 | 187 | -- Default to just converting the value to a string. 188 | return tostring(value) 189 | end 190 | 191 | -- Updates values within a table. 192 | function nut.db.update(tableName, data, condition, callback, limit) 193 | assert(type(data) == "table", "data is not a table") 194 | 195 | -- Start generating the query. 196 | local query = "UPDATE "..tostring(tableName) 197 | 198 | -- Convert the table into an update query. 199 | local updates = {} 200 | 201 | for k, v in pairs(data) do 202 | updates[#updates + 1] = "`"..tostring(k).."`="..nut.db.toString(v) 203 | end 204 | 205 | -- Don't do anything if data is empty. 206 | if (#updates == 0) then 207 | return 208 | end 209 | 210 | -- Add the columns and values to the query. 211 | query = query.." SET "..table.concat(updates, ",") 212 | 213 | -- Add the condition if given. 214 | if (condition) then 215 | query = query.." WHERE "..tostring(condition) 216 | end 217 | 218 | -- Add the limit if given. 219 | if (limit) then 220 | query = query.." LIMIT "..tostring(limit) 221 | end 222 | 223 | nut.db.query(query, callback) 224 | end 225 | 226 | nut.db.connect(nut.db.modules.sqlite) 227 | 228 | -- Actually connect once everything is done loading. 229 | hook.Add("PluginInitialized", "nutDatabaseConnect", function() 230 | -- Connect to the database if applicable. 231 | if (nut.db.sqlModule) then 232 | nut.db.sqlModule.connect(nut.db.connectCallback) 233 | end 234 | end) -------------------------------------------------------------------------------- /gamemode/shared.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: shared.lua 3 | Purpose: Loads all of the NutScript framework components. 4 | --]] 5 | 6 | nut.loading = true 7 | 8 | -- Create a table to store NutScript classes. 9 | nut.meta = nut.meta or {} 10 | 11 | -- Set NUT_BASE to another gamemode to have NutScript derive from it. 12 | if (type(NUT_BASE) == "string") then 13 | DeriveGamemode(NUT_BASE) 14 | end 15 | 16 | -- Include utility functions. 17 | include("util.lua") 18 | 19 | -- Include the framework files. 20 | nut.util.includeDir("thirdparty") 21 | nut.util.includeDir("classes") 22 | nut.util.includeDir("libraries") 23 | 24 | -- Set some gamemode information. 25 | GM.Name = "NutScript 2" 26 | GM.Author = "Chessnut" 27 | 28 | -- Loads the framework related files within the derived gamemode. 29 | function GM:PreGamemodeLoaded() 30 | nut.plugin.initialize() 31 | nut.loading = false 32 | end 33 | 34 | -- Loads the framework related files after a refresh occured. 35 | function GM:OnReloaded() 36 | nut.plugin.initialize() 37 | nut.loading = false 38 | end -------------------------------------------------------------------------------- /gamemode/thirdparty/sh_pon.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianhang/nutscript2/c2f94b199f7e6979f447d050531411121a43ec84/gamemode/thirdparty/sh_pon.lua -------------------------------------------------------------------------------- /gamemode/util.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: util.lua 3 | Purpose: Sets up the utility "library" by providing the utility function to 4 | load files and folders. Then, the actual utility functions are 5 | included. 6 | --]] 7 | 8 | -- The character before the trailing slash of a path. 9 | local BEFORE_SLASH = -2 10 | 11 | nut.util = nut.util or {} 12 | 13 | -- Includes a file with handling of state using the file's name. 14 | function nut.util.include(path, state) 15 | if ((state == "server" or path:find("sv_")) and SERVER) then 16 | return include(path) 17 | elseif (state == "client" or path:find("cl_")) then 18 | if (SERVER) then 19 | AddCSLuaFile(path) 20 | else 21 | return include(path) 22 | end 23 | elseif (state == "shared" or path:find("sh_")) then 24 | if (SERVER) then 25 | AddCSLuaFile(path) 26 | end 27 | 28 | return include(path) 29 | end 30 | end 31 | 32 | -- Includes files within a folder with handling of state. 33 | function nut.util.includeDir(directory, state, relative) 34 | -- Get where to start searching for files from. 35 | local baseDir 36 | 37 | if (relative) then 38 | baseDir = "" 39 | else 40 | local gamemode = GM and GM.FolderName or engine.ActiveGamemode() 41 | or "nutscript2" 42 | 43 | baseDir = gamemode.."/gamemode/" 44 | end 45 | 46 | -- Remove the trailing slash if it exists. 47 | if (directory:sub(-1, -1) == "/") then 48 | directory = directory:sub(1, TRAILING_SLASH) 49 | end 50 | 51 | -- Include all files within the directory. 52 | for _, path in ipairs(file.Find(baseDir..directory.."/*.lua", "LUA")) do 53 | nut.util.include(baseDir..directory.."/"..path, state) 54 | end 55 | end 56 | 57 | -- Include the utility functions. 58 | nut.util.includeDir("utilities") 59 | -------------------------------------------------------------------------------- /gamemode/utilities/cl_blur.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: utilities/cl_blur.lua 3 | Purpose: Creates utility functions that are used to blur parts of the screen. 4 | --]] 5 | 6 | -- THe default blur intensity. 7 | local BLUR_INTENSITY = 5 8 | 9 | -- Create a configuration for the blurring method. 10 | NUT_CVAR_CHEAPBLUR = CreateClientConVar("nut_chearblur", 0, true) 11 | 12 | -- Create a variable to store the cheap blur state. 13 | local useCheapBlur = NUT_CVAR_CHEAPBLUR:GetBool() 14 | 15 | cvars.AddChangeCallback("nut_cheapblur", function(name, old, new) 16 | useCheapBlur = (tonumber(new) or 0) > 0 17 | end) 18 | 19 | -- Get the blurring material. 20 | local blur = Material("pp/blurscreen") 21 | 22 | -- Draws a blurred material over a panel. 23 | function nut.util.drawBlur(panel, amount, passes) 24 | -- Intensity of the blur. 25 | amount = amount or BLUR_INTENSITY 26 | 27 | if (useCheapBlur) then 28 | surface.SetDrawColor(50, 50, 50, amount * 20) 29 | surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall()) 30 | else 31 | surface.SetMaterial(blur) 32 | surface.SetDrawColor(255, 255, 255) 33 | 34 | local x, y = panel:LocalToScreen(0, 0) 35 | 36 | for i = -(passes or 0.2), 1, 0.2 do 37 | -- Do things to the blur material to make it blurry. 38 | blur:SetFloat("$blur", i * amount) 39 | blur:Recompute() 40 | 41 | -- Draw the blur material over the screen. 42 | render.UpdateScreenEffectTexture() 43 | surface.DrawTexturedRect(-x, -y, ScrW(), ScrH()) 44 | end 45 | end 46 | end 47 | 48 | -- Draws a blurred material over a certain area of the screen. 49 | function nut.util.drawBlurAt(x, y, w, h, amount, passes) 50 | -- Intensity of the blur. 51 | amount = amount or BLUR_INTENSITY 52 | 53 | if (useCheapBlur) then 54 | surface.SetDrawColor(50, 50, 50, amount * 20) 55 | surface.DrawRect(x, y, w, h) 56 | else 57 | surface.SetMaterial(blur) 58 | surface.SetDrawColor(255, 255, 255) 59 | 60 | local scrW, scrH = ScrW(), ScrH() 61 | local x2, y2 = x / scrW, y / scrH 62 | local w2, h2 = (x + w) / scrW, (y + h) / scrH 63 | 64 | for i = -(passes or 0.2), 1, 0.2 do 65 | blur:SetFloat("$blur", i * amount) 66 | blur:Recompute() 67 | 68 | render.UpdateScreenEffectTexture() 69 | surface.DrawTexturedRectUV(x, y, w, h, x2, y2, w2, h2) 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /gamemode/utilities/cl_resolution.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: utilities/cl_resolution.lua 3 | Purpose: Sets up a hook that is triggered when the screen resolution changes. 4 | --]] 5 | 6 | -- How often a screen size check should be done. 7 | local CHECK_INTERVAL = 1 8 | 9 | local LAST_WIDTH = ScrW() 10 | local LAST_HEIGHT = ScrH() 11 | 12 | timer.Create("nutResolutionMonitor", CHECK_INTERVAL, 0, function() 13 | -- Get the current screen width. 14 | local scrW, scrH = ScrW(), ScrH() 15 | 16 | -- Check if anything is different. 17 | if (scrW ~= LAST_WIDTH or scrH ~= LAST_HEIGHT) then 18 | hook.Run("ScreenResolutionChanged", LAST_WIDTH, LAST_HEIGHT) 19 | 20 | LAST_WIDTH = scrW 21 | LAST_HEIGHT = scrH 22 | end 23 | end) 24 | -------------------------------------------------------------------------------- /gamemode/utilities/cl_text.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: utilities/cl_text.lua 3 | Purpose: Creates utility functions for drawing text on the screen. 4 | --]] 5 | 6 | -- How transparent a text's shadow should be compared to the text color. 7 | local SHADOW_ALPHA_RATIO = 0.67 8 | 9 | -- Indices for text position. 10 | local POSITION_X = 1 11 | local POSITION_Y = 2 12 | 13 | -- Create a generic font to use in case one is not given. 14 | surface.CreateFont("nutGenericFont", { 15 | font = "Arial", 16 | size = 16, 17 | weight = 500 18 | }) 19 | 20 | -- Create a table to store text data. 21 | local TEXT_DATA = {pos = {0, 0}} 22 | 23 | -- Draw a text with a shadow. 24 | function nut.util.drawText(text, x, y, color, alignX, alignY, font, alpha) 25 | -- Set the default color to white. 26 | color = color or color_white 27 | 28 | -- Set up the text. 29 | TEXT_DATA.text = text 30 | TEXT_DATA.font = font or "nutGenericFont" 31 | TEXT_DATA.pos[POSITION_X] = x 32 | TEXT_DATA.pos[POSITION_Y] = y 33 | TEXT_DATA.color = color 34 | TEXT_DATA.xalign = alignX or 0 35 | TEXT_DATA.yalign = alignY or 0 36 | 37 | -- Draw the text. 38 | return draw.TextShadow(TEXT_DATA, 1, 39 | alpha or (color.a * SHADOW_ALPHA_RATIO)) 40 | end 41 | 42 | -- Wraps text so it does not pass a certain width. 43 | function nut.util.wrapText(text, maxWidth, font) 44 | assert(type(text) == "string", "text is not a string") 45 | assert(type(maxWidth) == "number", "maxWidth is not a number") 46 | 47 | -- Set the surface font so size data works. 48 | font = font or "nutGenericFont" 49 | surface.SetFont(font) 50 | 51 | -- Get information about the given text. 52 | local exploded = string.Explode("%s", text, true) 53 | local line = "" 54 | local lines = {} 55 | local w = surface.GetTextSize(text) 56 | local maxW = 0 57 | 58 | -- Don't wrap if it is not needed. 59 | if (w <= maxWidth) then 60 | return {(text:gsub("%s", " "))}, w 61 | end 62 | 63 | -- Otherwise, break up the text into words and wrap when a certain word 64 | -- makes a line too long. 65 | for i = 1, #exploded do 66 | local word = exploded[i] 67 | line = line.." "..word 68 | w = surface.GetTextSize(line) 69 | 70 | if (w > maxWidth) then 71 | lines[#lines + 1] = line 72 | line = "" 73 | 74 | if (w > maxW) then 75 | maxW = w 76 | end 77 | end 78 | end 79 | 80 | if (line ~= "") then 81 | lines[#lines + 1] = line 82 | end 83 | 84 | return lines, maxW 85 | end 86 | -------------------------------------------------------------------------------- /gamemode/utilities/sh_door.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Name: utilities/sh_door.lua 3 | Purpose: Creates utility functions that relates to doors. 4 | --]] 5 | 6 | local ENTITY = FindMetaTable("Entity") 7 | 8 | -- The default time to respawn a door in seconds. 9 | local DOOR_RESPAWN = 120 10 | 11 | -- How much force to push the door's ragdoll by. 12 | local DOOR_FORCE = 100 13 | 14 | -- Returns whether or not an entity is a door. 15 | function ENTITY:isDoor() 16 | return self:GetClass():find("door") 17 | end 18 | 19 | -- Returns which door is connected to this door. 20 | function ENTITY:getDoorPartner() 21 | return self.nutPartner 22 | end 23 | 24 | if (SERVER) then 25 | -- Blasts a door down. 26 | function ENTITY:blastDoor(velocity, lifeTime, ignorePartner) 27 | -- Ignore blasting a door if this is not even a door. 28 | if (not self:isDoor()) then 29 | return 30 | end 31 | 32 | -- Remove the dummy if it already exists. 33 | if (IsValid(self.nutDummy)) then 34 | self.nutDummy:Remove() 35 | end 36 | 37 | velocity = velocity or VectorRand()*DOOR_FORCE 38 | lifeTime = lifeTime or DOOR_RESPAWN 39 | 40 | -- Blast the partner door too if applicable. 41 | local partner = self:getDoorPartner() 42 | 43 | if (IsValid(partner) and not ignorePartner) then 44 | partner:blastDoor(velocity, lifeTime, true) 45 | end 46 | 47 | -- Create a dummy door that resembles this door. 48 | local color = self:GetColor() 49 | 50 | local dummy = ents.Create("prop_physics") 51 | dummy:SetModel(self:GetModel()) 52 | dummy:SetPos(self:GetPos()) 53 | dummy:SetAngles(self:GetAngles()) 54 | dummy:Spawn() 55 | dummy:SetColor(color) 56 | dummy:SetMaterial(self:GetMaterial()) 57 | dummy:SetSkin(self:GetSkin() or 0) 58 | dummy:SetRenderMode(RENDERMODE_TRANSALPHA) 59 | dummy:CallOnRemove("restoreDoor", function() 60 | if (IsValid(self)) then 61 | self:SetNotSolid(false) 62 | self:SetNoDraw(false) 63 | self:DrawShadow(true) 64 | self.nutIgnoreUse = false 65 | self.nutIsMuted = false 66 | 67 | for k, v in ipairs(ents.FindByClass("prop_door_rotating")) do 68 | if (v:GetParent() == self) then 69 | v:SetNotSolid(false) 70 | v:SetNoDraw(false) 71 | 72 | if (v.onDoorRestored) then 73 | v:onDoorRestored(self) 74 | end 75 | end 76 | end 77 | end 78 | end) 79 | dummy:SetOwner(self) 80 | dummy:SetCollisionGroup(COLLISION_GROUP_WEAPON) 81 | 82 | for k, v in ipairs(self:GetBodyGroups()) do 83 | dummy:SetBodygroup(v.id, self:GetBodygroup(v.id)) 84 | end 85 | 86 | -- Push the door in the given direction. 87 | dummy:GetPhysicsObject():SetVelocity(velocity) 88 | 89 | -- Make this door uninteractable. 90 | self:Fire("unlock") 91 | self:Fire("open") 92 | self:SetNotSolid(true) 93 | self:SetNoDraw(true) 94 | self:DrawShadow(false) 95 | self.nutDummy = dummy 96 | self.nutIsMuted = true 97 | self.nutIgnoreUse = true 98 | self:DeleteOnRemove(dummy) 99 | 100 | -- Notify other doors that this has been blasted. 101 | for k, v in ipairs(ents.FindByClass("prop_door_rotating")) do 102 | if (v:GetParent() == self) then 103 | v:SetNotSolid(true) 104 | v:SetNoDraw(true) 105 | 106 | if (v.onDoorBlasted) then 107 | v:onDoorBlasted(self) 108 | end 109 | end 110 | end 111 | 112 | -- Bring back the old door after the given time has passed. 113 | local uniqueID = "doorRestore"..self:EntIndex() 114 | local uniqueID2 = "doorOpener"..self:EntIndex() 115 | 116 | timer.Create(uniqueID2, 1, 0, function() 117 | if (IsValid(self) and IsValid(self.nutDummy)) then 118 | self:Fire("open") 119 | else 120 | timer.Remove(uniqueID2) 121 | end 122 | end) 123 | 124 | timer.Create(uniqueID, lifeTime, 1, function() 125 | if (IsValid(self) and IsValid(dummy)) then 126 | uniqueID = "dummyFade"..dummy:EntIndex() 127 | local alpha = 255 128 | 129 | timer.Create(uniqueID, 0.1, 255, function() 130 | if (IsValid(dummy)) then 131 | alpha = alpha - 1 132 | dummy:SetColor(ColorAlpha(color, alpha)) 133 | 134 | if (alpha <= 0) then 135 | dummy:Remove() 136 | end 137 | else 138 | timer.Remove(uniqueID) 139 | end 140 | end) 141 | end 142 | end) 143 | 144 | return dummy 145 | end 146 | end 147 | 148 | -- Find all of the door partners. 149 | hook.Add("InitPostEntity", "nutDoorPartnerFinder", function() 150 | local doors = ents.FindByClass("prop_door_rotating") 151 | 152 | -- Match every door with its partner. 153 | for _, door in ipairs(doors) do 154 | local door2 = door:GetOwner() 155 | 156 | if (IsValid(parent) and parent:isDoor()) then 157 | door.nutPartner = door2 158 | door2.nutPartner = door 159 | else 160 | for _, door2 in ipairs(doors) do 161 | if (door2:GetOwner() == door) then 162 | door.nutPartner = door2 163 | door2.nutPartner = door 164 | 165 | break 166 | end 167 | end 168 | end 169 | end 170 | end) 171 | -------------------------------------------------------------------------------- /gamemode/utilities/sh_entity.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Name: utilities/sh_entity.lua 3 | Purpose: Provides some utility functions that deal with entities. 4 | --]] 5 | 6 | local ENTITY = FindMetaTable("Entity") 7 | 8 | -- A list of chair entities. 9 | local CHAIR_CACHE = {} 10 | 11 | -- Add chair models that were defined as chairs. 12 | for _, vehicle in pairs(list.Get("Vehicles")) do 13 | if (vehicle.Category == "Chairs") then 14 | CHAIR_CACHE[vehicle.Model:lower()] = true 15 | end 16 | end 17 | 18 | -- Returns whether or not a vehicle is a chair. 19 | function ENTITY:isChair() 20 | return CHAIR_CACHE[string.lower(self.GetModel(self))] 21 | end 22 | 23 | -- Returns whether or not an entity is locked. 24 | function ENTITY:isLocked() 25 | if (self:IsVehicle()) then 26 | local data = self:GetSaveTable() 27 | 28 | if (data) then 29 | return data.VehicleLocked 30 | end 31 | else 32 | local data = self:GetSaveTable() 33 | 34 | if (data) then 35 | return data.m_bLocked 36 | end 37 | end 38 | 39 | return false 40 | end 41 | 42 | -- Returns the entity that is blocking this entity. 43 | function ENTITY:getBlocker() 44 | local data = self:GetSaveTable() 45 | 46 | return data.pBlocker 47 | end 48 | 49 | -- Adds support for muting entities with entity.nutMuted. 50 | hook.Add("EntityEmitSound", "nutEntityMute", function(data) 51 | if (data.Entity.nutMuted) then 52 | return false 53 | end 54 | end) 55 | 56 | -- Adds support for preventing entity use with entity.nutIgnoreUse. 57 | hook.Add("PlayerUse", "nutEntityIgnoreUse", function(client, entity) 58 | if (entity.nutIgnoreUse) then 59 | return false 60 | end 61 | end) 62 | -------------------------------------------------------------------------------- /gamemode/utilities/sh_player.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: utilities/sh_player.lua 3 | Purpose: Creates utility functions relating to players on the server. 4 | --]] 5 | 6 | -- How often to check if a player is staring at an entity for stared actions. 7 | local STARE_INTERVAL = 0.1 8 | 9 | -- The maximum distance between a stared entity and player. 10 | local STARE_DISTANCE = 96 11 | 12 | -- How far back to check for a drop position. 13 | local DROP_POS_BACK = 64 14 | 15 | -- How far front to check for a drop position. 16 | local DROP_POS_FRONT = 86 17 | 18 | -- How much to pad the drop position. 19 | local DROP_POS_PAD = 36 20 | 21 | -- A barrier between walking speed and running speed. 22 | local SPEED_BARRIER = 10 23 | 24 | -- Create an table of female model paths. 25 | -- Each entry should be FEMALE_MODELS[model] = true 26 | FEMALE_MODELS = {} 27 | 28 | -- Player metatable for extending. 29 | local PLAYER = FindMetaTable("Player") 30 | 31 | -- Finds a player whose name matches a string. 32 | function nut.util.findPlayer(name, allowPatterns) 33 | assert(type(name) == "string", "name is not a string") 34 | 35 | -- Try finding direct matches first. 36 | for _, client in ipairs(player.GetAll()) do 37 | if (client:Name() == name) then 38 | return client 39 | end 40 | end 41 | 42 | -- Then try a looser search. 43 | if (not allowPatterns) then 44 | name = string.PatternSafe(name) 45 | end 46 | 47 | for _, client in ipairs(player.GetAll()) do 48 | if (nut.util.stringMatches(client:Name(), name)) then 49 | return client 50 | end 51 | end 52 | end 53 | 54 | -- Returns a table of admin players. 55 | function nut.util.getAdmins(superOnly) 56 | local found = {} 57 | 58 | for _, client in ipairs(player.GetAll()) do 59 | if (superOnly and client:IsSuperAdmin()) then 60 | found[#found + 1] = client 61 | elseif (not superOnly and v:IsAdmin()) then 62 | found[#found + 1] = client 63 | end 64 | end 65 | 66 | return found 67 | end 68 | 69 | -- Check if a player is using a female player model. 70 | function PLAYER:isFemale() 71 | local model = self:GetModel():lower() 72 | 73 | return FEMALE_MODELS[model] 74 | or model:find("female") 75 | or model:find("alyx") 76 | or model:find("mossman") 77 | end 78 | 79 | -- Perform an action after a player has stared at an entity for a while. 80 | function PLAYER:doStaredAction(entity, time, callback, onCancel, distance) 81 | assert(type(callback) == "function", "callback is not a function") 82 | assert(type(time) == "number", "time is not a number") 83 | 84 | local timerID = "nutStare"..self:UniqueID() 85 | local trace = {filter = self} 86 | 87 | -- A function to cancel the stared action. 88 | local function cancelFunc() 89 | timer.Remove(timerID) 90 | 91 | if (onCancel) then 92 | onCancel() 93 | end 94 | end 95 | 96 | -- Make sure the player is staring at the entity. Once the time is up, 97 | -- run the callback. 98 | timer.Create(timerID, STARE_INTERVAL, time / STARE_INTERVAL, function() 99 | if (IsValid(self) and IsValid(entity)) then 100 | trace.start = self:GetShootPos() 101 | trace.endpos = trace.start 102 | + self:GetAimVector()*(distance or STARE_DISTANCE) 103 | 104 | if (util.TraceLine(trace).Entity ~= entity) then 105 | cancelFunc() 106 | elseif (callback and timer.RepsLeft(timerID) == 0) then 107 | callback() 108 | end 109 | else 110 | cancelFunc() 111 | end 112 | end) 113 | 114 | -- Return the cancel function so the user can cancel the action themselves. 115 | return cancelFunc 116 | end 117 | 118 | -- Find a player to drop an entity in front of a player. 119 | function PLAYER:getItemDropPos() 120 | local trace = util.TraceLine({ 121 | start = self:GetShootPos() - self:GetAimVector()*DROP_POS_BACK, 122 | endpos = self:GetShootPos() + self:GetAimVector()*DROP_POS_FRONT, 123 | filter = self 124 | }) 125 | 126 | return trace.HitPos + trace.HitNormal*DROP_POS_PAD 127 | end 128 | 129 | -- Check whether or not the player is stuck in something. 130 | function PLAYER:isStuck() 131 | return util.TraceEntity({ 132 | start = self:GetPos(), 133 | endpos = self:GetPos(), 134 | filter = self 135 | }, self).StartSolid 136 | end 137 | 138 | local length2D = FindMetaTable("Vector").Length2D 139 | 140 | -- Check whether or not a player is running. 141 | function PLAYER:isRunning() 142 | return length2D(self.GetVelocity(self)) > 143 | (self.GetWalkSpeed(self) + SPEED_BARRIER) 144 | end 145 | -------------------------------------------------------------------------------- /gamemode/utilities/sh_sound.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: utilities/sh_sound.lua 3 | Purpose: Creates a utility function for playing queued sounds. 4 | --]] 5 | 6 | local ADJUST_SOUND = SoundDuration("npc/metropolice/pain1.wav") > 0 7 | and "" or "../../hl2/sound/" 8 | 9 | -- Emits sounds one after the other from an entity. 10 | function nut.util.emitQueuedSounds(entity, sounds, delay, spacing, 11 | volume, pitch) 12 | assert(type(sounds) == "table", "sounds is not a table") 13 | 14 | -- Let there be a delay before any sound is played. 15 | delay = tonumber(delay) or 0 16 | spacing = tonumber(spacing) or 0.1 17 | 18 | -- Loop through all of the sounds. 19 | for k, v in ipairs(sounds) do 20 | local postSet, preSet = 0, 0 21 | 22 | -- Determine if this sound has special time offsets. 23 | if (type(v) == "table") then 24 | postSet, preSet = tonumber(v[2]) or 0, tonumber(v[3]) or 0 25 | v = v[1] 26 | end 27 | 28 | -- Get the length of the sound. 29 | local length = SoundDuration(ADJUST_SOUND..v) 30 | 31 | -- If the sound has a pause before it is played, add it here. 32 | delay = delay + preSet 33 | 34 | -- Have the sound play in the future. 35 | timer.Simple(delay, function() 36 | -- Check if the entity still exists and play the sound. 37 | if (IsValid(entity)) then 38 | entity:EmitSound(tostring(v), tonumber(volume), tonumber(pitch)) 39 | end 40 | end) 41 | 42 | -- Add the delay for the next sound. 43 | delay = delay + length + postSet + spacing 44 | end 45 | 46 | -- Return how long it took for the whole thing. 47 | return delay 48 | end 49 | -------------------------------------------------------------------------------- /gamemode/utilities/sh_string.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | File: utilities/sh_string.lua 3 | Purpose: Creates utility functions that deals with strings. 4 | --]] 5 | 6 | -- Returns whether or a not a string matches. 7 | function nut.util.stringMatches(a, b) 8 | if (type(a) == "string" and type(b) == "string") then 9 | -- Check if the actual letters match. 10 | if (a == b) then return true end 11 | 12 | -- Try checking the lowercase version. 13 | local a2, b2 = a:lower(), b:lower() 14 | 15 | if (a2 == b2) then return true end 16 | 17 | -- Be less strict and search. 18 | if (a:find(b)) then return true end 19 | if (a2:find(b2)) then return true end 20 | end 21 | 22 | return false 23 | end 24 | -------------------------------------------------------------------------------- /nutscript2.txt: -------------------------------------------------------------------------------- 1 | "nutscript2" 2 | { 3 | "base" "sandbox" 4 | "title" "NutScript 2" 5 | "author" "Chessnut" 6 | } 7 | --------------------------------------------------------------------------------