Helix is a framework for roleplay gamemodes in [Garry's Mod](https://gmod.facepunch.com/), based off of [NutScript 1.1](https://github.com/rebel1324/NutScript). Helix provides a stable, feature-filled, open-source, and DRM-free base so you can focus more on the things you want: making gameplay.
## Getting Started
Visit the getting started guide in the [documentation](https://docs.gethelix.co/manual/getting-started/) for an in-depth guide.
If you know what you're doing, a quick start for bootstrapping your own schema is forking/copying the skeleton schema at https://github.com/nebulouscloud/helix-skeleton. The skeleton contains all the important elements you need to have a functioning schema so you can get to coding right away.
You can also use our HL2 RP schema at https://github.com/nebulouscloud/helix-hl2rp as a base to work off of if you need something more fleshed out.
## Plugins
If you'd like to enhance your gamemode, you can use any of the freely provided plugins available at the [Helix Plugin Center](https://plugins.gethelix.co). It is also encouraged to submit your own plugins for others to find and use at https://github.com/nebulouscloud/helix-plugins
## Documentation
Up-to-date documentation can be found at https://docs.gethelix.co. This is automatically updated when commits are pushed to the master branch.
If you'd like to ask some questions or integrate with the community, you can always join our [Discord](https://discord.gg/2AutUcF) server. We highly encourage you to search through the documentation before posting a question - the docs contain a good deal of information about how the various systems in Helix work, and it might explain what you're looking for.
### Building documentation
If you're planning on contributing to the documentation, you'll probably want to preview your changes before you commit. The documentation can be built using [LDoc](https://github.com/impulsh/ldoc) - note that we use a forked version to add some functionality. You'll need [LuaRocks](https://luarocks.org/) installed in order to get started.
```shell
# installing ldoc
git clone https://github.com/impulsh/ldoc
cd ldoc
luarocks make
# navigate to the helix repo folder and run
ldoc .
```
You may not see the syntax highlighting work on your local copy - you'll need to copy the files in `docs/js` and `docs/css` over into the `docs/html` folder after it's done building.
## Contributing
Feel free to submit a pull request with any fixes/changes that you might find beneficial. Currently, there are no solid contributing guidelines other than keeping your code consistent with the rest of the framework.
## Acknowledgements
Helix is a fork of NutScript 1.1 by [Chessnut](https://github.com/brianhang) and [rebel1324](https://github.com/rebel1324).
================================================
FILE: config.ld
================================================
file = {
"gamemode",
"plugins",
"docs/hooks",
exclude = {"gamemode/core/libs/thirdparty"}
}
module_file = {
Character = "gamemode/core/meta/sh_character.lua",
Entity = "gamemode/core/meta/sh_entity.lua",
Inventory = "gamemode/core/meta/sh_inventory.lua",
Item = "gamemode/core/meta/sh_item.lua",
Player = "gamemode/core/meta/sh_player.lua"
}
dir = "docs/html"
project = "Helix"
title = "Helix Documentation"
no_space_before_args = true
style = "docs/css"
template = "docs/templates"
format = "markdown"
ignore = true
topics = "docs/manual"
use_markdown_titles = true
kind_names = {module = "Libraries", topic = "Manual"}
merge = true
sort = true
sort_modules = true
simple_args_string = true -- we show optionals/defaults outside of the display name
strip_metamethod_prefix = true -- remove the name of the table when displaying metamethod names
no_viewed_topic_at_top = true -- don't put the currently viewed topic at the top
use_new_templates = true -- new templating system
pretty_urls = true -- avoid showing .html in urls
pretty_topic_names = true -- strips extension from manual filenames, this does not check filename collisions
custom_tags = {
{"realm", hidden = true},
{"internal", hidden = true}
}
custom_display_name_handler = function(item, default_handler)
if (item.type == "function" and item.module) then
if (item.module.type == "classmod" or item.module.type == "panel") then
return item.module.mod_name .. ":" .. default_handler(item)
elseif (item.module.type == "hooks") then
return item.module.mod_name:upper() .. ":" .. default_handler(item)
end
end
return default_handler(item)
end
new_type("hooks", "Hooks", true)
new_type("panel", "Panels", true)
-- helix types
tparam_alias("char", "Character")
tparam_alias("inventory", "Inventory")
tparam_alias("item", "Item")
tparam_alias("ixtype", "ix.type")
tparam_alias("date", "date")
-- standard types
tparam_alias("string", "string")
tparam_alias("bool", "boolean")
tparam_alias("func", "function")
tparam_alias("player", "Player")
tparam_alias("entity", "Entity")
tparam_alias("color", "color")
tparam_alias("tab", "table")
tparam_alias("material", "material")
tparam_alias("vector", "vector")
tparam_alias("angle", "angle")
================================================
FILE: docs/css/highlight.css
================================================
/*
github.com style (c) Vasily Polovnyov
*/
.hljs {
display: block;
color: #333;
}
.hljs-comment,
.hljs-quote {
color: #535346;
font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-subst {
color: #333;
font-weight: bold;
}
.hljs-number,
.hljs-literal,
.hljs-variable,
.hljs-template-variable,
.hljs-tag .hljs-attr {
color: #008080;
}
.hljs-string,
.hljs-doctag {
color: #d14;
}
.hljs-title,
.hljs-section,
.hljs-selector-id {
color: #900;
font-weight: bold;
}
.hljs-subst {
font-weight: normal;
}
.hljs-type,
.hljs-class .hljs-title {
color: #458;
font-weight: bold;
}
.hljs-tag,
.hljs-name,
.hljs-attribute {
color: #000080;
font-weight: normal;
}
.hljs-regexp,
.hljs-link {
color: #009926;
}
.hljs-symbol,
.hljs-bullet {
color: #990073;
}
.hljs-built_in,
.hljs-builtin-name {
color: #0086b3;
}
.hljs-meta {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
================================================
FILE: docs/css/ldoc.css
================================================
:root {
--content-width: 1200px;
--sidebar-width: 330px;
--padding-big: 48px;
--padding-normal: 24px;
--padding-small: 16px;
--padding-tiny: 10px;
--font-massive: 32px;
--font-huge: 24px;
--font-big: 18px;
--font-normal: 16px;
--font-tiny: 12px;
--font-style-normal: Segoe UI, Helvetica, Arial, sans-serif;
--font-style-code: Consolas, monospace;
--color-accent: rgb(115, 53, 142);
--color-accent-dark: rgb(85, 39, 105);
--color-white: rgb(255, 255, 255);
--color-offwhite: rgb(200, 200, 200);
--color-white-accent: rgb(203, 190, 209);
--color-black: rgb(0, 0, 0);
--color-lightgrey: rgb(160, 160, 160);
--color-background-light: rgb(240, 240, 240);
--color-background-dark: rgb(33, 33, 33);
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background-color: var(--color-background-light);
font-family: var(--font-style-normal);
display: flex;
flex-direction: column;
}
a {
color: inherit;
text-decoration: inherit;
}
h1, h2, h3, h4 {
font-weight: 400;
}
ul li {
margin-left: var(--padding-small);
}
/* landing */
.landing {
background-color: var(--color-accent);
color: var(--color-white);
padding: 128px 0 128px 0;
}
.landing h1 {
margin: 0;
padding: 0;
border: none;
font-weight: 100;
font-size: var(--font-massive);
text-align: center;
}
.wrapper {
padding: var(--padding-small);
}
details {
user-select: none;
}
details summary {
outline: none;
}
code {
font-family: "Source Code Pro", monospace;
font-size: 85%;
white-space: pre;
tab-size: 4;
-moz-tab-size: 4;
padding: 2px 4px;
background-color: rgb(33, 33, 33, 0.1);
}
pre {
background-color: rgb(33, 33, 33, 0.1);
margin-top: var(--padding-small);
padding: var(--padding-tiny);
overflow: auto;
}
pre code {
background-color: transparent;
}
span.realm {
width: 14px;
height: 14px;
border-radius: 3px;
display: inline-block;
margin-right: 6px;
}
span.realm.shared {
background: linear-gradient(45deg, #f80 0%, #f80 50%, #08f 51%, #08f 100%);
}
span.realm.client {
background-color: #f80;
}
span.realm.server {
background-color: #08f;
}
/* wrapper element for sidebar/content */
main {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
width: var(--content-width);
margin: auto;
}
/* sidebar */
nav {
color: var(--color-offwhite);
background-color: var(--color-background-dark);
position: fixed;
display: flex;
flex-direction: column;
width: var(--sidebar-width);
height: 100%;
}
/* sidebar header */
nav header {
color: var(--color-white);
background-color: var(--color-accent);
padding: var(--padding-small);
}
nav header h1 {
font-size: var(--font-huge);
font-weight: 100;
text-align: center;
margin-bottom: var(--padding-small);
}
#search {
background-color: var(--color-accent-dark);
color: var(--color-white);
border: none;
font-size: var(--font-normal);
outline: none;
width: 100%;
padding: 6px;
}
#search::placeholder {
color: var(--color-white-accent);
}
#search::-webkit-search-cancel-button {
display: none;
}
/* sidebar contents */
nav section {
padding: var(--padding-small);
overflow: auto;
}
nav section ul {
list-style-type: none;
}
nav section::-webkit-scrollbar,
pre::-webkit-scrollbar {
width: 8px;
height: 8px;
}
nav section::-webkit-scrollbar-track,
pre::-webkit-scrollbar-track {
background: transparent;
}
nav section::-webkit-scrollbar-thumb {
background-color: var(--color-lightgrey);
}
pre::-webkit-scrollbar-thumb {
background-color: var(--color-lightgrey);
}
/* sidebar contents category */
nav section details.category {
padding-top: var(--padding-tiny);
}
nav section details.category > ul > li {
margin: 0;
line-height: 1.5;
}
nav section details.category > ul > li a {
display: inline-block;
width: 90%;
}
nav section details.category:first-of-type {
padding-top: calc(var(--padding-tiny) * -1);
}
nav section details.category summary::-webkit-details-marker {
opacity: 0.5;
cursor: pointer;
}
nav section details.category summary h2 {
color: var(--color-accent);
font-size: var(--font-big);
letter-spacing: 2px;
text-transform: uppercase;
cursor: pointer;
padding-bottom: var(--padding-tiny);
}
/* content */
article {
background-color: rgb(255, 255, 255);
width: calc(100% - var(--sidebar-width));
min-height: 100vh;
margin-left: var(--sidebar-width);
}
article .wrapper > *:first-child {
margin-top: 0;
}
/* header */
article header {
color: rgb(255, 255, 255);
background-color: rgb(115, 53, 142);
padding: var(--padding-tiny);
}
article header h1 {
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
padding-bottom: 8px;
font-family: var(--font-style-code);
margin: 0;
}
article header h2 {
padding-top: var(--padding-tiny);
margin: 0;
font-size: var(--font-normal);
font-weight: normal;
}
article header.module a {
color: white !important;
text-decoration: underline;
}
details.category > summary {
list-style: none;
}
details.category > summary::-webkit-details-marker {
display: none;
}
article h1 {
font-size: 28px;
font-weight: 600;
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
margin-top: 24px;
margin-bottom: 16px;
padding-bottom: 8px;
}
article h2 {
font-size: 20px;
font-weight: 600;
margin-top: 12px;
}
article h3 {
color: rgb(115, 53, 142);
margin-top: var(--padding-tiny);
text-transform: uppercase;
}
article p {
margin-top: var(--padding-small);
}
article p a,
article ul li a,
article h1 a,
article h2 a {
color: rgb(115, 53, 142);
font-weight: 600;
}
article h1.title {
color: rgb(255, 255, 255);
background-color: rgb(115, 53, 142);
margin-top: var(--padding-small);
margin-bottom: 0;
padding: var(--padding-tiny);
font-size: var(--font-big);
font-weight: 100;
letter-spacing: 2px;
text-transform: uppercase;
}
a.reference {
color: rgb(115, 53, 142);
float: right;
margin-top: 8px;
padding-left: 8px;
font-size: 14px;
font-weight: 600;
}
.notice {
--color-notice-background: var(--color-accent);
--color-notice-text: var(--color-notice-background);
margin-top: var(--padding-tiny);
border: 2px solid var(--color-notice-background);
}
.notice.error {
--color-notice-background: rgb(194, 52, 130);
}
.notice.warning {
--color-notice-background: rgb(224, 169, 112);
--color-notice-text: rgb(167, 104, 37);
}
.notice .title {
color: var(--color-white);
background-color: var(--color-notice-background);
padding: var(--padding-tiny);
font-size: var(--font-normal);
text-transform: uppercase;
letter-spacing: 2px;
}
.notice p {
color: var(--color-notice-text);
margin: 0 !important;
padding: var(--padding-tiny);
}
/* function/table */
.method {
display: flex;
flex-flow: column;
background-color: rgb(230, 230, 230);
padding: var(--padding-tiny);
margin-top: var(--padding-small);
}
.method header {
color: rgb(0, 0, 0);
background-color: inherit;
padding: 0;
order: -1;
}
.method header .anchor {
color: inherit;
text-decoration: inherit;
}
.method header .anchor:target h1 {
background-color: rgba(115, 53, 142, 0.2);
background-clip: content-box;
}
.method header h1 {
font-family: "Source Code Pro", monospace;
padding-bottom: var(--padding-tiny);
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
font-size: 20px;
}
.method header p:first-of-type {
margin-top: var(--padding-tiny);
}
.method h3 {
color: rgb(115, 53, 142);
font-size: var(--font-normal);
letter-spacing: 2px;
text-transform: uppercase;
}
.method pre {
margin-top: var(--padding-tiny);
}
@media only screen and (max-width: 1100px) {
main nav {
position: inherit;
}
main article {
margin-left: 0;
}
}
.method ul {
margin-top: var(--padding-tiny);
background-color: inherit;
}
.method ul li {
list-style: none;
margin: 4px 0 0 var(--padding-normal);
}
.method ul li:first-of-type {
margin-top: 0;
}
.method ul li p {
margin: 4px 0 0 var(--padding-normal);
}
.method ul li pre {
margin: 4px 0 0 var(--padding-normal);
}
.method ul li a {
color: rgb(115, 53, 142);
font-weight: 600;
}
/* we have to manually specify these instead of making a shared class since you cannot customize the parameter class in ldoc */
.parameter, .type, .default {
display: inline-block;
color: rgb(255, 255, 255) !important;
padding: 4px;
font-size: 14px;
font-family: "Source Code Pro", monospace;
}
.parameter {
background-color: rgb(115, 53, 142);
}
.type {
background-color: rgb(31, 141, 155);
}
a.type {
font-weight: 300 !important;
text-decoration: underline;
}
.default {
background-color: rgb(193, 114, 11);
}
.type a {
padding: 0;
}
.or {
color: rgba(115, 53, 142, 0.5);
background-color: inherit;
width: calc(100% - 32px);
height: 8px;
margin: 0 0 8px 32px;
text-align: center;
font-weight: 600;
border-bottom: 1px solid rgba(115, 53, 142, 0.5);
}
.or span {
background-color: inherit;
padding: 0 8px 0 8px;
}
================================================
FILE: docs/hooks/class.lua
================================================
-- luacheck: ignore 111
--[[--
Class setup hooks.
As with `Faction`s, `Class`es get their own hooks for when players leave/join a class, etc. These hooks are only
valid in class tables that are created in `schema/classes/sh_classname.lua`, and cannot be used like regular gamemode hooks.
]]
-- @hooks Class
--- Whether or not a player can switch to this class.
-- @realm shared
-- @player client Client that wants to switch to this class
-- @treturn bool True if the player is allowed to switch to this class
-- @usage function CLASS:CanSwitchTo(client)
-- return client:IsAdmin() -- only admins allowed in this class!
-- end
function CanSwitchTo(client)
end
--- Called when a character has left this class and has joined a different one. You can get the class the character has
-- has joined by calling `character:GetClass()`.
-- @realm server
-- @player client Player who left this class
function OnLeave(client)
end
--- Called when a character has joined this class.
-- @realm server
-- @player client Player who has joined this class
-- @usage function CLASS:OnSet(client)
-- client:SetModel("models/police.mdl")
-- end
function OnSet(client)
end
--- Called when a character in this class has spawned in the world.
-- @realm server
-- @player client Player that has just spawned
function OnSpawn(client)
end
================================================
FILE: docs/hooks/faction.lua
================================================
-- luacheck: ignore 111
--[[--
Faction setup hooks.
Factions get their own hooks that are called for various reasons, but the most common one is to set up a character
once it's created and assigned to a certain faction. For example, giving a police faction character a weapon on creation.
These hooks are used in faction tables that are created in `schema/factions/sh_factionname.lua` and cannot be used like
regular gamemode hooks.
]]
-- @hooks Faction
--- Called when the default name for a character needs to be retrieved (i.e upon initial creation).
-- @realm shared
-- @player client Client to get the default name for
-- @treturn string Default name for the newly created character
-- @usage function FACTION:GetDefaultName(client)
-- return "MPF-RCT." .. tostring(math.random(1, 99999))
-- end
function GetDefaultName(client)
end
--- Called when a character has been initally created and assigned to this faction.
-- @realm server
-- @player client Client that owns the character
-- @char character Character that has been created
-- @usage function FACTION:OnCharacterCreated(client, character)
-- local inventory = character:GetInventory()
-- inventory:Add("pistol")
-- end
function OnCharacterCreated(client, character)
end
--- Called when a character in this faction has spawned in the world.
-- @realm server
-- @player client Player that has just spawned
function OnSpawn(client)
end
--- Called when a player's character has been transferred to this faction.
-- @realm server
-- @char character Character that was transferred
-- @usage function FACTION:OnTransferred(character)
-- character:SetModel(self.models[1])
-- end
function OnTransferred(character)
end
================================================
FILE: docs/hooks/plugin.lua
================================================
-- luacheck: ignore 111
--[[--
Global hooks for general use.
Plugin hooks are regular hooks that can be used in your schema with `Schema:HookName(args)`, in your plugin with
`PLUGIN:HookName(args)`, or in your addon with `hook.Add("HookName", function(args) end)`.
]]
-- @hooks Plugin
--- Adjusts the data used just before creating a new character.
-- @realm server
-- @player client Player that is creating the character
-- @tab payload Table of data to be used for character creation
-- @tab newPayload Table of data be merged with the current payload
-- @usage function PLUGIN:AdjustCreationPayload(client, payload, newPayload)
-- newPayload.money = payload.attributes["stm"] -- Sets the characters initial money to the stamina attribute value.
-- end
function AdjustCreationPayload(client, payload, newPayload)
end
--- Adjusts a player's current stamina offset amount. This is called when the player's stamina is about to be changed; every
-- `0.25` seconds on the server, and every frame on the client.
-- @realm shared
-- @player client Player whose stamina is changing
-- @number baseOffset Amount the stamina is changing by. This can be a positive or negative number depending if they are
-- exhausting or regaining stamina
-- @treturn number New offset to use
-- @usage function PLUGIN:AdjustStaminaOffset(client, baseOffset)
-- return baseOffset * 2 -- Drain/Regain stamina twice as fast.
-- end
function AdjustStaminaOffset(client, baseOffset)
end
--- Creates the business panel in the tab menu.
-- @realm client
-- @treturn bool Whether or not to create the business menu
-- @usage function PLUGIN:BuildBusinessMenu()
-- return LocalPlayer():IsAdmin() -- Only builds the business menu for admins.
-- end
function BuildBusinessMenu()
end
--- Whether or not a message can be auto formatted with punctuation and capitalization.
-- @realm server
-- @player speaker Player that sent the message
-- @string chatType Chat type of the message. This will be something registered with `ix.chat.Register` - like `ic`, `ooc`, etc.
-- @string text Unformatted text of the message
-- @treturn bool Whether or not to allow auto formatting on the message
-- @usage function PLUGIN:CanAutoFormatMessage(speaker, chatType, text)
-- return false -- Disable auto formatting outright.
-- end
function CanAutoFormatMessage(speaker, chatType, text)
end
--- Whether or not certain information can be displayed in the character info panel in the tab menu.
-- @realm client
-- @tab suppress Information to **NOT** display in the UI - modify this to change the behaviour. This is a table of the names of
-- some panels to avoid displaying. Valid names include:
--
-- - `time` - current in-game time
-- - `name` - name of the character
-- - `description` - description of the character
-- - `characterInfo` - entire panel showing a list of additional character info
-- - `faction` - faction name of the character
-- - `class` - name of the character's class if they're in one
-- - `money` - current money the character has
-- - `attributes` - attributes list for the character
--
-- Note that schemas/plugins can add additional character info panels.
-- @usage function PLUGIN:CanCreateCharacterInfo(suppress)
-- suppress.attributes = true -- Hides the attributes panel from the character info tab
-- end
function CanCreateCharacterInfo(suppress)
end
--- Whether or not the ammo HUD should be drawn.
-- @realm client
-- @entity weapon Weapon the player currently is holding
-- @treturn bool Whether or not to draw the ammo hud
-- @usage function PLUGIN:CanDrawAmmoHUD(weapon)
-- if (weapon:GetClass() == "weapon_frag") then -- Hides the ammo hud when holding grenades.
-- return false
-- end
-- end
function CanDrawAmmoHUD(weapon)
end
--- Called when a player tries to use abilities on the door, such as locking.
-- @realm shared
-- @player client The client trying something on the door.
-- @entity door The door entity itself.
-- @number access The access level used when called.
-- @treturn bool Whether or not to allow the client access.
-- @usage function PLUGIN:CanPlayerAccessDoor(client, door, access)
-- return true -- Always allow access.
-- end
function CanPlayerAccessDoor(client, door, access)
end
--- Whether or not a player is allowed to combine an item `other` into the given `item`.
-- @realm server
-- @player client Player attempting to combine an item into another
-- @number item instance ID of the item being dropped onto
-- @number other instance ID of the item being combined into the first item, this can be invalid due to it being from clientside
-- @treturn bool Whether or not to allow the player to combine the items
-- @usage function PLUGIN:CanPlayerCombineItem(client, item, other)
-- local otherItem = ix.item.instances[other]
--
-- if (otherItem and otherItem.uniqueID == "soda") then
-- return false -- disallow combining any item that has a uniqueID equal to `soda`
-- end
-- end
function CanPlayerCombineItem(client, item, other)
end
--- Whether or not a player is allowed to create a new character with the given payload.
-- @realm server
-- @player client Player attempting to create a new character
-- @tab payload Data that is going to be used for creating the character
-- @treturn bool Whether or not the player is allowed to create the character. This function defaults to `true`, so you
-- should only ever return `false` if you're disallowing creation. Otherwise, don't return anything as you'll prevent any other
-- calls to this hook from running.
-- @treturn string Language phrase to use for the error message
-- @treturn ... Arguments to use for the language phrase
-- @usage function PLUGIN:CanPlayerCreateCharacter(client, payload)
-- if (!client:IsAdmin()) then
-- return false, "notNow" -- only allow admins to create a character
-- end
-- end
-- -- non-admins will see the message "You are not allowed to do this right now!"
function CanPlayerCreateCharacter(client, payload)
end
--- Whether or not a player is allowed to drop the given `item`.
-- @realm server
-- @player client Player attempting to drop an item
-- @number item instance ID of the item being dropped
-- @treturn bool Whether or not to allow the player to drop the item
-- @usage function PLUGIN:CanPlayerDropItem(client, item)
-- return false -- Never allow dropping items.
-- end
function CanPlayerDropItem(client, item)
end
--- Whether or not a player can earn money at regular intervals. This hook runs only if the player's character faction has
-- a salary set - i.e `FACTION.pay` is set to something other than `0` for their faction.
-- @realm server
-- @player client Player to give money to
-- @tab faction Faction of the player's character
-- @treturn bool Whether or not to allow the player to earn salary
-- @usage function PLUGIN:CanPlayerEarnSalary(client, faction)
-- return client:IsAdmin() -- Restricts earning salary to admins only.
-- end
function CanPlayerEarnSalary(client, faction)
end
--- Whether or not the player is allowed to enter observer mode. This is allowed only for admins by default and can be
-- customized by server owners if the server is using a CAMI-compliant admin mod.
-- @realm server
-- @player client Player attempting to enter observer
-- @treturn bool Whether or not to allow the player to enter observer
-- @usage function PLUGIN:CanPlayerEnterObserver(client)
-- return true -- Always allow observer.
-- end
function CanPlayerEnterObserver(client)
end
--- Whether or not a player can equip the given `item`. This is called for items with `outfit`, `pacoutfit`, or `weapons` as
-- their base. Schemas/plugins can utilize this hook for their items.
-- @realm server
-- @player client Player attempting to equip the item
-- @tab item Item being equipped
-- @treturn bool Whether or not to allow the player to equip the item
-- @see CanPlayerUnequipItem
-- @usage function PLUGIN:CanPlayerEquipItem(client, item)
-- return client:IsAdmin() -- Restrict equipping items to admins only.
-- end
function CanPlayerEquipItem(client, item)
end
--- Whether or not a player is allowed to hold an entity with the hands SWEP.
-- @realm server
-- @player client Player attempting to hold an entity
-- @entity entity Entity being held
-- @treturn bool Whether or not to allow the player to hold the entity
-- @usage function PLUGIN:CanPlayerHoldObject(client, entity)
-- return !(client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) -- Disallow players in observer holding objects.
-- end
function CanPlayerHoldObject(client, entity)
end
--- Whether or not a player is allowed to interact with an entity's interaction menu if it has one.
-- @realm server
-- @player client Player attempting interaction
-- @entity entity Entity being interacted with
-- @string option Option selected by the player
-- @param data Any data passed with the interaction option
-- @treturn bool Whether or not to allow the player to interact with the entity
-- @usage function PLUGIN:CanPlayerInteractEntity(client, entity, option, data)
-- if (entity:GetClass() == "my_big_entity" and entity:GetPos():Distance(client:GetPos()) < 192) then
-- return true -- Force allow interacting if within larger than default interact range of large entity
-- end
--
-- if (client:GetNetVar("drunk")) then
-- return false -- Disallow interacting with an entity while drunk
-- end
-- end
function CanPlayerInteractEntity(client, entity, option, data)
end
--- Whether or not a player is allowed to interact with an item via an inventory action (e.g picking up, dropping, transferring
-- inventories, etc). Note that this is for an item *table*, not an item *entity*. This is called after `CanPlayerDropItem`
-- and `CanPlayerTakeItem`.
-- @realm server
-- @player client Player attempting interaction
-- @string action The action being performed
-- @param item Item's instance ID or item table
-- @param data Any data passed with the action
-- @treturn bool Whether or not to allow the player to interact with the item
-- @usage function PLUGIN:CanPlayerInteractItem(client, action, item, data)
-- return false -- Disallow interacting with any item.
-- end
function CanPlayerInteractItem(client, action, item, data)
end
--- Whether or not a plyer is allowed to join a class.
-- @realm shared
-- @player client Player attempting to join
-- @number class ID of the class
-- @tab info The class table
-- @treturn bool Whether or not to allow the player to join the class
-- @usage function PLUGIN:CanPlayerJoinClass(client, class, info)
-- return client:IsAdmin() -- Restrict joining classes to admins only.
-- end
function CanPlayerJoinClass(client, class, info)
end
--- Whether or not a player can knock on the door with the hands SWEP.
-- @realm server
-- @player client Player attempting to knock
-- @entity entity Door being knocked on
-- @treturn bool Whether or not to allow the player to knock on the door
-- @usage function PLUGIN:CanPlayerKnock(client, entity)
-- return false -- Disable knocking on doors outright.
-- end
function CanPlayerKnock(client, entity)
end
--- Whether or not a player can open a shipment spawned from the business menu.
-- @realm server
-- @player client Player attempting to open the shipment
-- @entity entity Shipment entity
-- @treturn bool Whether or not to allow the player to open the shipment
-- @usage function PLUGIN:CanPlayerOpenShipment(client, entity)
-- return client:Team() == FACTION_BMD -- Restricts opening shipments to FACTION_BMD.
-- end
function CanPlayerOpenShipment(client, entity)
end
--- Whether or not a player is allowed to spawn a container entity.
-- @realm server
-- @player client Player attempting to spawn a container
-- @string model Model of the container being spawned
-- @entity entity Container entity
-- @treturn bool Whether or not to allow the player to spawn the container
-- @usage function PLUGIN:CanPlayerSpawnContainer(client, model, entity)
-- return client:IsAdmin() -- Restrict spawning containers to admins.
-- end
function CanPlayerSpawnContainer(client, model, entity)
end
--- Whether or not a player is allowed to take an item and put it in their inventory.
-- @realm server
-- @player client Player attempting to take the item
-- @entity item Entity corresponding to the item
-- @treturn bool Whether or not to allow the player to take the item
-- @usage function PLUGIN:CanPlayerTakeItem(client, item)
-- return !(client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) -- Disallow players in observer taking items.
-- end
function CanPlayerTakeItem(client, item)
end
--- Whether or not the player is allowed to punch with the hands SWEP.
-- @realm shared
-- @player client Player attempting throw a punch
-- @treturn bool Whether or not to allow the player to punch
-- @usage function PLUGIN:CanPlayerThrowPunch(client)
-- return client:GetCharacter():GetAttribute("str", 0) > 0 -- Only allow players with strength to punch.
-- end
function CanPlayerThrowPunch(client)
end
--- Whether or not a player can trade with a vendor.
-- @realm server
-- @player client Player attempting to trade
-- @entity entity Vendor entity
-- @string uniqueID The uniqueID of the item being traded.
-- @bool isSellingToVendor If the client is selling to the vendor
-- @treturn bool Whether or not to allow the client to trade with the vendor
-- @usage function PLUGIN:CanPlayerTradeWithVendor(client, entity, uniqueID, isSellingToVendor)
-- return false -- Disallow trading with vendors outright.
-- end
function CanPlayerTradeWithVendor(client, entity, uniqueID, isSellingToVendor)
end
--- Whether or not a player can unequip an item.
-- @realm server
-- @player client Player attempting to unequip an item
-- @tab item Item being unequipped
-- @treturn bool Whether or not to allow the player to unequip the item
-- @see CanPlayerEquipItem
-- @usage function PLUGIN:CanPlayerUnequipItem(client, item)
-- return false -- Disallow unequipping items.
-- end
function CanPlayerUnequipItem(client, item)
end
--- Whether or not a player can buy an item from the business menu.
-- @realm shared
-- @player client Player that uses a business menu
-- @string uniqueID The uniqueID of the business menu item
-- @treturn bool Whether or not to allow the player to buy an item from the business menu
-- @usage function PLUGIN:CanPlayerUseBusiness(client, uniqueID)
-- return false -- Disallow buying from the business menu.
-- end
function CanPlayerUseBusiness(client, uniqueID)
end
--- Whether or not a player can use a character.
-- @realm shared
-- @player client Player that wants to use a character
-- @char character Character that a player wants to use
-- @treturn bool Whether or not to allow the player to load a character
-- @usage function PLUGIN:CanPlayerUseCharacter(client, character)
-- return false -- Disallow using any character.
-- end
function CanPlayerUseCharacter(client, character)
end
--- Whether or not a player can use a door.
-- @realm server
-- @player client Player that wants to use a door
-- @entity entity Door that a player wants to use
-- @treturn bool Whether or not to allow the player to use a door
-- @usage function PLUGIN:CanPlayerUseDoor(client, character)
-- return false -- Disallow using any door.
-- end
function CanPlayerUseDoor(client, entity)
end
--- Determines whether a player can use a vendor.
-- @realm server
-- @player activator The player attempting to use the vendor
-- @entity vendor The vendor entity being used
-- @treturn bool Returns false if the player can't use the vendor
function CanPlayerUseVendor(activator, vendor)
end
--- Whether or not a player can view his inventory.
-- @realm client
-- @treturn bool Whether or not to allow the player to view his inventory
-- @usage function PLUGIN:CanPlayerViewInventory()
-- return false -- Prevent player from viewing his inventory.
-- end
function CanPlayerViewInventory()
end
--- Whether or not to save a container.
-- @realm server
-- @entity entity Container entity to save
-- @tab inventory Container inventory
-- @treturn bool Whether or not to save a container
-- @usage function PLUGIN:CanSaveContainer(entity, inventory)
-- return false -- Disallow saving any container.
-- end
function CanSaveContainer(entity, inventory)
end
--- @realm shared
function CanTransferItem(item, currentInv, oldInv)
end
--- @realm shared
function CharacterAttributeBoosted(client, character, attribID, boostID, boostAmount)
end
--- @realm shared
function CharacterAttributeUpdated(client, self, key, value)
end
--- @realm shared
function CharacterDeleted(client, id, isCurrentChar)
end
--- @realm shared
function CharacterHasFlags(self, flags)
end
--- @realm shared
function CharacterLoaded(character)
end
--- Called when a character was saved.
-- @realm server
-- @char character that was saved
function CharacterPostSave(character)
end
--- @realm shared
function CharacterPreSave(character)
end
--- @realm shared
function CharacterRecognized()
end
--- Called when a character was restored.
-- @realm server
-- @char character that was restored
function CharacterRestored(character)
end
--- @realm shared
function CharacterVarChanged(character, key, oldVar, value)
end
--- @realm shared
function CharacterVendorTraded(client, entity, uniqueID, isSellingToVendor)
end
--- @realm client
function ChatboxCreated()
end
--- @realm client
function ChatboxPositionChanged(x, y, width, height)
end
--- @realm client
function ColorSchemeChanged(color)
end
--- Called when a container was removed.
-- @realm server
-- @entity container Container that was removed
-- @tab inventory Container inventory
function ContainerRemoved(container, inventory)
end
--- @realm client
function CreateCharacterInfo(panel)
end
--- @realm client
function CreateCharacterInfoCategory(panel)
end
--- @realm client
function CreateItemInteractionMenu(icon, menu, itemTable)
end
--- @realm client
function CreateMenuButtons(tabs)
end
--- Called when a shipment was created.
-- @realm server
-- @player client Player that ordered the shipment
-- @entity entity Shipment entity
function CreateShipment(client, entity)
end
--- Called when a server has connected to the database.
-- @realm server
function DatabaseConnected()
end
--- Called when a server failed to connect to the database.
-- @realm server
-- @string error Error that prevented server from connecting to the database
function DatabaseConnectionFailed(error)
end
--- @realm shared
function DoPluginIncludes(path, pluginTable)
end
--- @realm client
function DrawCharacterOverview()
end
--- @realm client
function DrawHelixModelView(panel, entity)
end
--- @realm client
function DrawPlayerRagdoll(entity)
end
--- @realm client
function GetCharacterDescription(client)
end
--- @realm shared
function GetCharacterName(speaker, chatType)
end
--- @realm shared
function GetChatPrefixInfo(text)
end
--- @realm client
function GetCrosshairAlpha(curAlpha)
end
--- @realm shared
function GetDefaultAttributePoints(client, count)
end
--- @realm shared
function GetDefaultCharacterName(client, faction)
end
--- @realm shared
function GetMaxPlayerCharacter(client)
end
--- Returns the sound to emit from the player upon death. If nothing is returned then it will use the default male/female death
-- sounds.
-- @realm server
-- @player client Player that died
-- @treturn[1] string Sound to play
-- @treturn[2] bool `false` if a sound shouldn't be played at all
-- @usage function PLUGIN:GetPlayerDeathSound(client)
-- -- play impact sound every time someone dies
-- return "physics/body/body_medium_impact_hard1.wav"
-- end
-- @usage function PLUGIN:GetPlayerDeathSound(client)
-- -- don't play a sound at all
-- return false
-- end
function GetPlayerDeathSound(client)
end
--- @realm client
function GetPlayerEntityMenu(client, options)
end
--- @realm client
function GetPlayerIcon(speaker)
end
--- Returns the sound to emit from the player upon getting damage.
-- @realm server
-- @player client Client that received damage
-- @treturn string Sound to emit
-- @usage function PLUGIN:GetPlayerPainSound(client)
-- return "NPC_MetroPolice.Pain" -- Make players emit MetroPolice pain sound.
-- end
function GetPlayerPainSound(client)
end
--- @realm shared
function GetPlayerPunchDamage(client, damage, context)
end
--- Returns the salary that character should get instead of his faction salary.
-- @realm server
-- @player client Client that is receiving salary
-- @tab faction Faction of the player's character
-- @treturn number Character salary
-- @see CanPlayerEarnSalary
-- @usage function PLUGIN:GetSalaryAmount(client, faction)
-- return 0 -- Everyone get no salary.
-- end
function GetSalaryAmount(client, faction)
end
--- @realm client
function GetTypingIndicator(character, text)
end
--- Registers chat classes after the core framework chat classes have been registered. You should usually create your chat
-- classes in this hook - especially if you want to reference the properties of a framework chat class.
-- @realm shared
-- @usage function PLUGIN:InitializedChatClasses()
-- -- let's say you wanted to reference an existing chat class's color
-- ix.chat.Register("myclass", {
-- format = "%s says \"%s\"",
-- GetColor = function(self, speaker, text)
-- -- make the chat class slightly brighter than the "ic" chat class
-- local color = ix.chat.classes.ic:GetColor(speaker, text)
--
-- return Color(color.r + 35, color.g + 35, color.b + 35)
-- end,
-- -- etc.
-- })
-- end
-- @see ix.chat.Register
-- @see ix.chat.classes
function InitializedChatClasses()
end
--- @realm shared
function InitializedConfig()
end
--- @realm shared
function InitializedPlugins()
end
--- @realm shared
function InitializedSchema()
end
--- Called when an item was added to the inventory.
-- @realm server
-- @tab oldInv Previous item inventory
-- @tab inventory New item inventory
-- @tab item Item that was added to the inventory
function InventoryItemAdded(oldInv, inventory, item)
end
--- Called when an item was removed from the inventory.
-- @realm server
-- @tab inventory Inventory from which item was removed
-- @tab item Item that was removed from the inventory
function InventoryItemRemoved(inventory, item)
end
--- @realm shared
function IsCharacterRecognized(character, id)
end
--- @realm client
function IsPlayerRecognized(client)
end
--- @realm client
function IsRecognizedChatType(chatType)
end
--- Called when server is loading data.
-- @realm server
function LoadData()
end
--- @realm client
function LoadFonts(font, genericFont)
end
--- @realm client
function LoadIntro()
end
--- @realm client
function MenuSubpanelCreated(subpanelName, panel)
end
--- @realm client
function MessageReceived(client, info)
end
--- @realm client
function OnAreaChanged(oldID, newID)
end
--- @realm shared
function OnCharacterCreated(client, character)
end
--- Called when a player who uses a character has disconnected.
-- @realm server
-- @player client The player that has disconnected
-- @char character The character that the player was using
function OnCharacterDisconnect(client, character)
end
--- Called when a character was ragdolled or unragdolled.
-- @realm server
-- @player client Player that was ragdolled or unradolled
-- @entity entity Ragdoll that represents the player
-- @bool bFallenOver Whether or not the character was ragdolled or unragdolled
function OnCharacterFallover(client, entity, bFallenOver)
end
--- Called when a character has gotten up from the ground.
-- @realm server
-- @player client Player that has gotten up
-- @entity ragdoll Ragdoll used to represent the player
function OnCharacterGetup(client, ragdoll)
end
--- @realm client
function OnCharacterMenuCreated(panel)
end
--- Called whenever an item entity has spawned in the world. You can access the entity's item table with
-- `entity:GetItemTable()`.
-- @realm server
-- @entity entity Spawned item entity
-- @usage function PLUGIN:OnItemSpawned(entity)
-- local item = entity:GetItemTable()
-- -- do something with the item here
-- end
function OnItemSpawned(entity)
end
--- @realm shared
function OnItemTransferred(item, curInv, inventory)
end
--- @realm client
function OnLocalVarSet(key, var)
end
--- @realm client
function OnPAC3PartTransferred(part)
end
--- Called when a player has picked up the money from the ground.
-- @realm server
-- @player client Player that picked up the money
-- @entity self Money entity
-- @treturn bool Whether or not to allow the player to pick up the money
-- @usage function PLUGIN:OnPickupMoney(client, self)
-- return false -- Disallow picking up money.
-- end
function OnPickupMoney(client, self)
end
--- @realm shared
function OnPlayerAreaChanged(client, oldID, newID)
end
--- Called when a player has entered or exited the observer mode.
-- @realm server
-- @player client Player that entered or exited the observer mode
-- @bool state Previous observer state
function OnPlayerObserve(client, state)
end
--- Called when a player has selected the entity interaction menu option while interacting with a player.
-- @realm server
-- @player client Player that other player has interacted with
-- @player callingClient Player that has interacted with with other player
-- @string option Option that was selected
function OnPlayerOptionSelected(client, callingClient, option)
end
--- Called when a player has purchased or sold a door.
-- @realm server
-- @player client Player that has purchased or sold a door
-- @entity entity Door that was purchased or sold
-- @bool bBuying Whether or not the player is bying a door
-- @func bCallOnDoorChild Function to call something on the door child
function OnPlayerPurchaseDoor(client, entity, bBuying, bCallOnDoorChild)
end
--- Called when a player was restricted.
-- @realm server
-- @player client Player that was restricted
function OnPlayerRestricted(client)
end
--- Called when a player was unrestricted.
-- @realm server
-- @player client Player that was unrestricted
function OnPlayerUnRestricted(client)
end
--- Called when a saved items were loaded.
-- @realm server
-- @tab loadedItems Table of items that were loaded
function OnSavedItemLoaded(loadedItems)
end
--- Called when server database are being wiped.
-- @realm server
function OnWipeTables()
end
--- @realm shared
function PlayerEnterSequence(client, sequence, callback, time, bNoFreeze)
end
--- Called when a player has interacted with an entity through the entity's interaction menu.
-- @realm server
-- @player client Player that performed interaction
-- @entity entity Entity being interacted with
-- @string option Option selected by the player
-- @param data Any data passed with the interaction option
function PlayerInteractEntity(client, entity, option, data)
end
--- Called when a player has interacted with an item.
-- @realm server
-- @player client Player that interacted with an item
-- @string action Action selected by the player
-- @tab item Item being interacted with
function PlayerInteractItem(client, action, item)
end
--- Called when a player has joined a class.
-- @realm server
-- @player client Player that has joined a class
-- @number class Index of the class player has joined to
-- @number oldClass Index of the player's previous class
function PlayerJoinedClass(client, class, oldClass)
end
--- @realm shared
function PlayerLeaveSequence(entity)
end
--- Called when a player has loaded a character.
-- @realm server
-- @player client Player that has loaded a character
-- @char character Character that was loaded
-- @char currentChar Character that player was using
function PlayerLoadedCharacter(client, character, currentChar)
end
--- Called when a player has locked a door.
-- @realm server
-- @player client Player that has locked a door
-- @entity door Door that was locked
-- @entity partner Door partner
function PlayerLockedDoor(client, door, partner)
end
--- Called when a player has locked a vehicle.
-- @realm server
-- @player client Player that has locked a vehicle
-- @entity vehicle Vehicle that was locked
function PlayerLockedVehicle(client, vehicle)
end
--- Called when player has said something in the text chat.
-- @realm server
-- @player speaker Player that has said something in the text chat
-- @string chatType Type of the chat that player used
-- @string text Chat message that player send
-- @bool anonymous Whether or not message was anonymous
-- @tab receivers Players who will hear that message
-- @string rawText Chat message without any formatting
-- @treturn string You can return text that will be shown instead
-- @usage function PLUGIN:PlayerMessageSend(speaker, chatType, text, anonymous, receivers, rawText)
-- return "Text" -- When a player writes something into chat, he will say "Text" instead.
-- end
function PlayerMessageSend(speaker, chatType, text, anonymous, receivers, rawText)
end
--- Called when a player model was changed.
-- @realm server
-- @player client Player whose model was changed
-- @string oldModel Old player model
function PlayerModelChanged(client, oldModel)
end
--- Called when a player has got stamina.
-- @realm server
-- @player client Player who has got stamina
function PlayerStaminaGained(client)
end
--- Called when a player has lost stamina.
-- @realm server
-- @player client Player who has lost stamina
function PlayerStaminaLost(client)
end
--- @realm shared
function PlayerThrowPunch(client, trace)
end
--- Called when a player has unlocked a door.
-- @realm server
-- @player client Player that has unlocked a door
-- @entity door Door that was unlocked
-- @entity partner Door partner
function PlayerUnlockedDoor(client, door, partner)
end
--- Called when a player has unlocked a vehicle.
-- @realm server
-- @player client Player that has unlocked a vehicle
-- @entity vehicle Vehicle that was unlocked
function PlayerUnlockedVehicle(client, vehicle)
end
--- Called when a player has used an entity.
-- @realm server
-- @player client Player who has used an entity
-- @entity entity Entity that was used by the player
function PlayerUse(client, entity)
end
--- Called when a player has used a door.
-- @realm server
-- @player client Player who has used a door
-- @entity entity Door that was used by the player
function PlayerUseDoor(client, entity)
end
--- @realm shared
function PlayerWeaponChanged(client, weapon)
end
--- @realm shared
function PluginLoaded(uniqueID, pluginTable)
end
--- @realm shared
function PluginShouldLoad(uniqueID)
end
--- @realm shared
function PluginUnloaded(uniqueID)
end
--- @realm client
function PopulateCharacterInfo(client, character, tooltip)
end
--- @realm client
function PopulateEntityInfo(entity, tooltip)
end
--- @realm client
function PopulateHelpMenu(categories)
end
--- @realm client
function PopulateImportantCharacterInfo(entity, character, tooltip)
end
--- @realm client
function PopulateItemTooltip(tooltip, item)
end
--- @realm client
function PopulatePlayerTooltip(client, tooltip)
end
--- @realm client
function PopulateScoreboardPlayerMenu(client, menu)
end
--- @realm client
function PostChatboxDraw(width, height, alpha)
end
--- @realm client
function PostDrawHelixModelView(panel, entity)
end
--- @realm client
function PostDrawInventory(panel)
end
--- Called when server data was loaded.
-- @realm server
function PostLoadData()
end
--- Called after player loadout.
-- @realm server
-- @player client
function PostPlayerLoadout(client)
end
--- Called after player has said something in the text chat.
-- @realm server
-- @player client Player that has said something in the text chat
-- @string chatType Type of the chat that player used
-- @string message Chat message that player send
-- @bool anonymous Whether or not message was anonymous
function PostPlayerSay(client, chatType, message, anonymous)
end
--- @realm shared
function PostSetupActs()
end
--- Called before character deletion.
-- @realm server
-- @player client Character owner
-- @char character Chraracter that will be deleted
function PreCharacterDeleted(client, character)
end
--- Called before character loading.
-- @realm server
-- @player client Player that loading a character
-- @char character Character that will be loaded
-- @char currentChar Character that player is using
function PrePlayerLoadedCharacter(client, character, currentChar)
end
--- Called before a message sent by a player is processed to be sent to other players - i.e this is ran as early as possible
-- and before things like the auto chat formatting. Can be used to prevent the message from being sent at all.
-- @realm server
-- @player client Player sending the message
-- @string chatType Chat class of the message
-- @string message Contents of the message
-- @bool bAnonymous Whether or not the player is sending the message anonymously
-- @treturn bool Whether or not to prevent the message from being sent
-- @usage function PLUGIN:PrePlayerMessageSend(client, chatType, message, bAnonymous)
-- if (!client:IsAdmin()) then
-- return false -- only allow admins to talk in chat
-- end
-- end
function PrePlayerMessageSend(client, chatType, message, bAnonymous)
end
--- Called when server is saving data.
-- @realm server
function SaveData()
end
--- @realm client
function ScreenResolutionChanged(width, height)
end
--- @realm shared
function SetupActs()
end
--- @realm shared
function SetupAreaProperties()
end
--- Called when a player has taken a shipment item.
-- @realm server
-- @player client Player that has taken a shipment item
-- @string uniqueID UniqueID of the shipment item that was taken
-- @number amount Amount of the items that were taken
function ShipmentItemTaken(client, uniqueID, amount)
end
--- @realm client
function ShouldBarDraw(bar)
end
--- Whether or not the server should delete saved items.
-- @realm server
-- @treturn bool Whether or not the server should delete saved items
-- @usage function PLUGIN:ShouldDeleteSavedItems()
-- return true -- Delete all saved items.
-- end
function ShouldDeleteSavedItems()
end
--- @realm client
function ShouldDisplayArea(newID)
end
--- @realm client
function ShouldDrawCrosshair(client, weapon)
end
--- @realm client
function ShouldDrawItemSize(item)
end
--- @realm client
function ShouldHideBars()
end
--- Whether or not a character should be permakilled upon death. This is only called if the `permakill` server config is
-- enabled.
-- @realm server
-- @player client Player to permakill
-- @char character Player's current character
-- @entity inflictor Entity that inflicted the killing blow
-- @entity attacker Other player or entity that killed the player
-- @treturn bool `false` if the player should not be permakilled
-- @usage function PLUGIN:ShouldPermakillCharacter(client, character, inflictor, attacker)
-- if (client:IsAdmin()) then
-- return false -- all non-admin players will have their character permakilled
-- end
-- end
function ShouldPermakillCharacter(client, character, inflictor, attacker)
end
--- Whether or not player should drown.
-- @realm server
-- @player client Player that is underwater
-- @treturn bool Whether or not player should drown
-- @usage function PLUGIN:ShouldPlayerDrowned(client)
-- return false -- Players will not drown.
-- end
function ShouldPlayerDrowned(client)
end
--- Whether or not remove player ragdoll on death.
-- @realm server
-- @player client Player that died
-- @treturn bool Whether or not remove player ragdoll on death
-- @usage function PLUGIN:ShouldRemoveRagdollOnDeath(client)
-- return false -- Player ragdolls will not be removed.
-- end
function ShouldRemoveRagdollOnDeath(client)
end
--- Whether or not to restore character inventory.
-- @realm server
-- @number characterID ID of the character
-- @number inventoryID ID of the inventory
-- @string inventoryType Type of the inventory
-- @treturn bool Whether or not to restore character inventory
-- @usage function PLUGIN:ShouldRestoreInventory(characterID, inventoryID, inventoryType)
-- return false -- Character inventories will not be restored.
-- end
function ShouldRestoreInventory(characterID, inventoryID, inventoryType)
end
--- @realm client
function ShouldShowPlayerOnScoreboard(client)
end
--- Whether or not spawn player ragdoll on death.
-- @realm server
-- @player client Player that died
-- @treturn bool Whether or not spawn player ragdoll on death
-- @usage function PLUGIN:ShouldSpawnClientRagdoll(client)
-- return false -- Player ragdolls will not be spawned.
-- end
function ShouldSpawnClientRagdoll(client)
end
--- @realm client
function ShowEntityMenu(entity)
end
--- @realm client
function ThirdPersonToggled(oldValue, value)
end
--- @realm client
function UpdateCharacterInfo(panel, character)
end
--- @realm client
function UpdateCharacterInfoCategory(panel, character)
end
--- Called when the distance on which the voice can be heard was changed.
-- @realm server
-- @number newValue New voice distance
function VoiceDistanceChanged(newValue)
end
--- @realm client
function WeaponCycleSound()
end
--- @realm client
function WeaponSelectSound(weapon)
end
================================================
FILE: docs/js/app.js
================================================
const skippedCategories = ["manual"];
class Node
{
constructor(name, element, expandable, noAutoCollapse, children = [])
{
this.name = name;
this.element = element;
this.expandable = expandable;
this.noAutoCollapse = noAutoCollapse;
this.children = children;
}
AddChild(name, element, expandable, noAutoCollapse, children)
{
let newNode = new Node(name, element, expandable, noAutoCollapse, children);
this.children.push(newNode);
return newNode;
}
}
class SearchManager
{
constructor(input, contents)
{
this.input = input;
this.input.addEventListener("input", event =>
{
this.OnInputUpdated(this.input.value.toLowerCase().replace(/:/g, "."));
});
// setup search tree
this.tree = new Node("", document.createElement("null"), true, true);
this.entries = {};
const categoryElements = contents.querySelectorAll(".category");
// iterate each kind (hooks/libraries/classes/etc)
for (const category of categoryElements)
{
const nameElement = category.querySelector(":scope > summary > h2");
if (!nameElement)
{
continue;
}
const categoryName = nameElement.textContent.trim().toLowerCase();
if (skippedCategories.includes(categoryName))
{
continue;
}
let categoryNode = this.tree.AddChild(categoryName, category, true, true);
const sectionElements = category.querySelectorAll(":scope > ul > li");
for (const section of sectionElements)
{
const entryElements = section.querySelectorAll(":scope > details > ul > li > a");
const sectionName = section.querySelector(":scope > details > summary > a")
.textContent
.trim()
.toLowerCase();
let sectionNode = categoryNode.AddChild(sectionName, section.querySelector(":scope > details"), true);
for (let i = 0; i < entryElements.length; i++)
{
const entryElement = entryElements[i];
const entryName = entryElement.textContent.trim().toLowerCase();
sectionNode.AddChild(sectionName + "." + entryName, entryElement.parentElement);
}
}
}
}
ResetVisibility(current)
{
current.element.style.display = "";
if (current.noAutoCollapse)
{
current.element.open = true;
}
else if (current.expandable)
{
current.element.open = false;
}
for (let node of current.children)
{
this.ResetVisibility(node);
}
}
Search(input, current)
{
let matched = false;
if (current.name.indexOf(input) != -1)
{
matched = true;
}
for (let node of current.children)
{
let childMatched = this.Search(input, node);
matched = matched || childMatched;
}
if (matched)
{
current.element.style.display = "";
if (current.expandable)
{
current.element.open = true;
}
}
else
{
current.element.style.display = "none";
if (current.expandable)
{
current.element.open = false;
}
}
return matched;
}
OnInputUpdated(input)
{
if (input.length <= 1)
{
this.ResetVisibility(this.tree);
return;
}
this.Search(input, this.tree);
}
}
window.onload = function()
{
const openDetails = document.querySelector(".category > ul > li > details[open]");
if (openDetails)
{
openDetails.scrollIntoView();
}
}
document.addEventListener("DOMContentLoaded", function()
{
const searchInput = document.getElementById("search");
const contents = document.querySelector("body > main > nav > section");
if (searchInput && contents)
{
new SearchManager(searchInput, contents);
}
});
================================================
FILE: docs/manual/converting-from-clockwork.md
================================================
# Clockwork to Helix Migration
If you are here, you probably want to be converting your code from another framework to Helix. Doing so should not be a difficult task. Most of the previous functions are probably within Helix in one form or another! This means all you need to do is match *x* function found in the old framework to *y* function in Helix. Some headings will contain a link - this will bring you to the documentation for Helix's equivalent library or class.
This tutorial assumes basic to intermediate knowledge and experience with Garry's Mod Lua.
**Before you start!** You will notice that Helix uses client for the variable that represents a player. Clockwork uses player for the variable instead, but this will conflict with the player library. So if you see `_player` being used in Clockwork, it means the Garry's Mod player library. This is just a preference and does not affect anything besides appear. So keep in mind throughout the tutorial, you may see player being used for Clockwork code and client being used for Helix code. They represent the same thing, just with a different name.
If you are converting Clockwork code to Helix, keep in mind that `_player` is not defined so you will need to either define `_player` yourself or switch it to player instead and change the variable name to client for player objects.
# Basics of Conversion
## Folders
Clockwork code and file structure is not too different from Helix. In the schema, the plugins folder and schema folder stay in the same place. There are some minor differences in naming however:
- The `schema/entities` folder should be moved outside out of the schema folder.
- The `libraries` folder needs to be renamed to `libs` to load.
- The `commands` tab will not load as each command is now defined in a single shared file, does not matter which one.
## Deriving from Helix
This is pretty important. If you want to use Helix as the base, you need to set it as the base. So, go to your Clockwork schema's `gamemode` folder. Inside should be two files: `init.lua `and `cl_init.lua`. Open both, and you should see something along the lines of `DeriveGamemode("Clockwork")`. Change this to `DeriveGamemode("helix")`.
# The Schema
## Introduction
Inside of the `schema` folder of the actual schema, you should see a file named `sh_schema.lua`. This is the main schema file in both Clockwork and Helix. Most of your changes may actually be within this file.
## Including Files
Both frameworks come with a utility function to include a file without worrying about sending them to the client and stuff. In Clockwork, this function is `Clockwork.kernel:IncludePrefixed("sh_myfile.lua")`. Change this to `ix.util.Include("sh_myfile.lua") `and save.
# The Plugin
## Introduction
Plugins serve as a means to add on to a schema or framework without directly modifying either. This allows for easier modifications that can be added/removed with ease. It is recommended that you keep all custom modifications left to plugins rather than editing the framework or the schema if possible.
## Structure
All plugins in Clockwork and Helix go into the `plugins` folder. However, there are many differences with the CW plugin structure. First of all, there are two things you see when you open a plugin folder: `plugin` again and `plugin.ini`.
Helix only has one file needed: `sh_plugin.lua` which acts like `sh_schema.lua` but for plugins.
## Conversion
The first step is to move all of the contents from the `plugin` folder to the main folder of the plugin folder. The `sh_plugin.lua` file needs to be changed to provide basic information about the plugin.You need to define three things in `sh_plugin.lua` which can be found within the `plugin.ini` file:
- `PLUGIN.name = "Plugin Name"`
- `PLUGIN.author = "Plugin Author"`
- `PLUGIN.description = "Plugin Description"`
If the plugin uses a special variable (e.g. `cwPluginName`) for the plugin, change it to `PLUGIN`.
- Note that the `PLUGIN` table is removed after the plugin is loaded. So if you want to use `PLUGIN` after the plugin has loaded (such as in console commands, in entities, etc.), add `local PLUGIN = PLUGIN` at the top.
- You can see if a global variable is defined for it by looking for `PLUGIN:SetGlobalAlias("cwMyPlugin")`. So, one would change `cwMyPlugin` to `PLUGIN`.
# The `Character` Object
One main thing that is very notable is how the character is referenced using `client:GetCharacter()` which returns a character object. The way the object works is just like an entity you spawn. It has its own properties like the model, color, etc. that makes it unique. You can access all the characters in a table which stores loaded characters with `ix.char.loaded`.
The character object comes with many predefined methods. You can look at how they are defined [by clicking here](https://github.com/NebulousCloud/helix/blob/master/gamemode/core/meta/sh_character.lua). The character object makes it very simple to manager character information.
You will notice throughout the framework, the character object is used a lot. The use of the character object makes a large barrier between what belongs to the character and what belongs to the player. For example: flags, models, factions, data, and other things are stored on the character and can be accessed by the character object.
In Clockwork, there is no use of an object. Instead, the character information is intertwined with the player object. For example:
```
-- in Clockwork
player:SetCharacterData("foo", "bar")
-- in Helix
client:GetCharacter():SetData("foo", "bar")
```
The use of the character object allows you to access other characters a player might own without needing to have them be the active character, or even access them when the player is not on the server. Overall, the use of the character object may seem like a complex concept, but will simplify a lot of things once you get the hang of the idea.
# The Libraries
## Animations (`ix.anim`)
Clockwork features many functions to set up animations for a specific model. Helix too has this functionality. Helix has one function instead that pairs a model to a specific "animation class" (grouping of animation types). So, all one needs to do is find the appropriate animation class to match the model with. Looking at the Clockwork function name should tell you.
```
-- before
Clockwork.animation:AddCivilProtectionModel("models/mymodel.mdl")
-- after
ix.anim.SetModelClass("models/mymodel.mdl", "metrocop")
```
## Attributes (`ix.attributes`)
Attributes allow the player to boost certain abilities over time. Both frameworks require one to register attributes, but they are done differently. In Clockwork, the `ATTRIBUTE` table needs to be defined and registered manually. In Helix, the `ATTRIBUTE` table is automatically defined and registered for you. All you need to do is have `ATTRIBUTE.value = "value"`. The basic parts of the attribute needed is `ATTRIBUTE.name` and `ATTRIBUTE.description`.
One extra feature for attributes in Helix is `ATTRIBUTE:OnSetup(client, value)` which is a function that gets called on spawn to apply any effects. For example, the stamina attribute changes the player's run speed by adding the amount of stamina points the player has.
You can find an example at [https://github.com/NebulousCloud/helix/blob/master/plugins/stamina/attributes/sh_stm.lua](https://github.com/NebulousCloud/helix/blob/master/plugins/stamina/attributes/sh_stm.lua)
## Classes (`ix.class`)
Classes are a part of the factions. They basically are a more specific form of a faction. Factions in Helix and Clockwork work similarly. For instance, all classes are placed in the `classes` folder under the schema folder and use `CLASS` as the main variable inside the file.
However:
- You do not need to use `local CLASS = Clockwork.class:New("My Class")`. Instead, `CLASS` is already defined for you and you set the name using `CLASS.name = "My Class"`
- `CLASS.factions` is *not* a table, so `CLASS.factions = {FACTION_MYFACTION}` becomes `CLASS.faction = FACTION_MYFACTION`
- You do not need to use `CLASS:Register()` as classes are registered for you after the file is done processing.
- Classes are *optional* for factions rather than being required.
## Commands (`ix.command`)
Commands no longer need to be in separate files. Instead, they are just placed into one large file. However, if you really wanted you can register multiple commands across multiple files or however you want. One thing you may notice is Clockwork uses a _COMMAND_ table while Helix does not always. It is simply a design preference. You can find examples at [https://github.com/NebulousCloud/helix/blob/master/gamemode/core/sh_commands.lua](https://github.com/NebulousCloud/helix/blob/master/gamemode/core/sh_commands.lua)
It should be noted that:
- `COMMAND.tip` is not used.
- `COMMAND.text` is not used.
- `COMMAND.flags` is not used.
- `COMMAND.arguments` does not need to be defined if no arguments are needed but is defined as a table of argument types when needed `arguments = {ix.type.character, ix.type.number}`. See `ix.command.CommandArgumentsStructure` for details.
- `COMMAND.access` for checking whether or not a person is a (super)admin can be replaced with `adminOnly = true` or `superAdminOnly = true` in the command table.
## Configurations (`ix.config`)
In Helix, the method of adding configurations that can be changed by server owners is heavily simplified. [See an example here](https://github.com/NebulousCloud/helix/blob/master/gamemode/config/sh_config.lua).
Adding a configuration is as follows:
```
-- before
Clockwork.config:Add("run_speed", 225)
-- after
ix.config.Add("runSpeed", 235, ...)
```
You'll notice that ellipses (...) were added at the end. This is because there are more arguments since adding configuration information has been placed into one function. Additionally:
- `Clockwork.config:ShareKey()` is not needed.
- The 3rd argument for `Clockwork.config:AddToSystem(name, key, description, min, max)` is also the 3rd argument for `ix.config.Add`
- The 4th argument for `ix.config.Add` is an optional function that is called when the configuration is changed.
- The 5th argument for `ix.config.Add` is a table. You can specify the category for the configuration to group it with other configurations. There is also a data table inside which can be used to determine the minimum value and maximum value for numbers. Check out [an example here](https://github.com/NebulousCloud/helix/blob/master/gamemode/config/sh_config.lua). See also `ix.config`.
## Currency (`ix.currency`)
Updating your currency code is simple:
```
-- before
Clockwork.config:SetKey("name_cash", "Tokens")
Clockwork.config:SetKey("name_cash", "Dollars") -- another example
-- after
ix.currency.Set("", "token", "tokens")
ix.currency.Set("$", "dollar", "dollars")
```
Note that you need to provide a symbol for that currency (€ for Euro, £ for Pound, ¥ for Yen, etc.) or just leave it as an empty string (`""`) and then provide the singular form of the name for the currency, then the plural form.
## Datastream
Helix uses the [net library](http://wiki.garrysmod.com/page/Net_Library_Usage) whereas Clockwork uses datastream ([netstream](https://github.com/alexgrist/NetStream/blob/master/netstream2.lua)).
If you're unfamiliar with the net library, you can include the netstream library to your schema by downloading [netstream](https://github.com/alexgrist/NetStream/blob/master/netstream2.lua) to `schema/libs/thirdparty/sh_netstream2.lua` and adding `ix.util.Include("libs/thirdparty/sh_netstream2.lua")` to your `sh_schema.lua` file.
Starting a datastream:
```
-- before
Clockwork.datastream:Start(receiver, "MessageName", {1, 2, 3});
-- after
netstream.Start(receiver, "MessageName", 1, 2, 3)
```
Receiving a datastream:
```
-- before
Clockwork.datastream:Hook("MessageName", function(player, data)
local a = data[1];
local b = data[2];
local c = data[3];
print(a, b, c);
end);
-- after
netstream.Hook("MessageName", function(client, a, b, c)
print(a, b, c)
end)
```
## Factions (`ix.faction`)
Factions, like classes, are pretty similar too. They share pretty much the same differences as classes in Clockwork and Helix do.
For instance:
- You do not need to use `local FACTION = Clockwork.faction:New("Name Here")`, instead `FACTION` is already defined for you and you set the name using `FACTION.name = "Name Here"`
- `FACTION.whitelist = true` is changed to `FACTION.isDefault = false`
- `FACTION.models` does not need a male and female part. Instead, all the models are combined into one big list.
- `function FACTION:GetName(name)` becomes `function FACTION:GetDefaultName(name)`
- `FACTION.description = "Describe me"` is added to the faction.
- `FACTION_MYFACTION = FACTION:Register()` becomes `FACTION_MYFACTION = FACTION.index`
## Flags (`ix.flag`)
Flags are functionally equivalent in Helix. To add a new flag:
```
-- before
Clockwork.flag:Add("x", "Name", "Description")
-- after
ix.flag.Add("x", "Description")
```
To check or manipulate a character's flag(s):
```
-- before
Clockwork.player:GiveFlags(player, flags)
Clockwork.player:TakeFlags(player, flags)
Clockwork.player:HasFlags(player, flags)
-- after
client:GetCharacter():GiveFlags(flags)
client:GetCharacter():TakeFlags(flags)
client:GetCharacter():HasFlags(flags)
```
## Inventories (`Inventory`)
Inventories have also had a change in the way they work that may seem very different than Clockwork. Similar to how characters are their own objects, inventories become their own objects as well. These inventory objects belong to character objects, which belongs to players. So, this creates a chain of objects which is neat. The use of inventories as objects makes it very simple to attach inventories to anything.
To access a player's inventory, you need to use `client:GetCharacter():GetInventory()` which returns the main inventory object for the player's character. You can also access all loaded inventories with `ix.item.inventories` but that is not important right now.
## Items (`Item`)
As discussed above, inventories contain items. Items are still used in inventories and world entities, use default class data, have callback functions, and can contain unique item data per instance.
### Setting up items
Every time needs to be registered, or have information about it (such as the name, model, what it does, etc.) defined. In Clockwork, you have your items defined in schemas/plugins under the items folder.
So let's start with the differences in structure in the item file.
- `local ITEM = Clockwork.item:New();` is removed
- `ITEM.uniqueID` is *completely* optional
- Replace `ITEM.cost` with `ITEM.price`
- `ITEM:Register()` is removed
### Item Sizes
Helix's inventory uses a grid and utilizes width and height instead of weight as a means of inventory capacity. This means you will have to change your item's weight (`ITEM.weight`) to something that might be analagous to the item's size using `ITEM.width` and `ITEM.height`. The item's size must be at least one by one grid cell. It's up to you to balance the sizes of items in your use case - taking into account how many items a character might have at once, the default inventory size set in the config, etc.
### Item Functions
Item functions are defined very differently than they are in Clockwork. For example:
```
-- before
function ITEM:OnUse(player, entity)
print("My name is: " .. player:Name(), entity)
end
-- after
ITEM.functions.Use = {
OnRun = function(item)
print("My name is: " .. item.player, item.entity)
end
}
```
All item functions are defined in the `ITEM.functions` table. This allows the drop-down menus when using the item a lot easier and cleaner to generate dynamically. There is also more control of the icons used for the options, whether or not the function should be displayed, etc.
You can see an example of a water item here: [https://github.com/NebulousCloud/helix-hl2rp/blob/master/schema/items/sh_water.lua](https://github.com/NebulousCloud/helix-hl2rp/blob/master/schema/items/sh_water.lua)
Here, we can define what happens when the function is run, what the icon is, and what sound it plays when used. It is basically put into one area rather than being scattered among hooks and stuff.
### Giving/Taking Items
So before we can give/take items, we need to understand what the *item instance* is. Using the analogy earlier about how the inventory system is like a forum, and inside the forum are posts (the items in this case), we can think of instancing an item as making a new post on a forum. So when we talk about an *item instance*, it is an item that has been created in the past. The reason we use an item instance (which is its own object too, neat!) is to make each item ever created unique. Each item instance can have its own data unique to itself.
Clockwork also uses an item instance system where you have to instance an item. So, to instance an item in Clockwork you would use:
```
item = Clockwork.item:CreateInstance("item")
```
And this would create a new instance of an item. Helix's instancing system is slightly different. Instead of having the function return the instance like it does in Clockwork, Helix relies on a callback to pass the instance. The reason for this is the item must be inserted into the database to get a unique number to represent that item. This is not done instantly, otherwise servers would freeze when new items are made. Clockwork uses the time and adds a number to get the numeric ID for an item, which allows the item to be returned which "solves" the issue, but I digress.
The Helix equivalent would be:
```
ix.item.Instance(0, "item", data, x, y, function(item) end)
```
Let's break down the differences:
- For Helix's item instance, the 1st argument (`0`) is the inventory that the item belongs to. You can specify 0 so it does not belong to any inventory.
- The data argument is *optional* and is just a table for the item data.
- *x* and *y* are the position of the items in inventory. You can find an available *x* and *y* with `inventory:FindEmptySlot()`.
- The function is an *optional* argument that passes the item instance. This is where you can directly access the new item.
Keep in mind that Helix will simplify the item system for you when it can. Normally, you would not need to instance an item yourself unless you were doing something advanced.
So you might be wondering, how do I spawn an item in the map, and how do I give a player an item? In Clockwork, you would do the following:
```
-- spawning an item in the map
Clockwork.entity:CreateItem(player, Clockwork.item:CreateInstance("item"), Vector(1, 2, 3));
-- giving a player an item
player:GiveItem(Clockwork.item:CreateInstance("item"));
```
The equivalent in Helix would be:
```
-- spawning an item in the map
ix.item.Spawn("item", Vector(1, 2, 3))
-- giving a player an item
client:GetCharacter():GetInventory():Add("test")
```
So in these two examples, the whole deal of instancing items is done for you in Helix!
# Hooks
You will need to modify the function name and arguments for your schema or plugin hooks.
```
-- before
function Schema:PlayerPlayPainSound(player, gender, damageInfo, hitGroup)
-- ...
end
-- after
function Schema:GetPlayerPainSound(client)
-- ...
end
```
You can see the documented hooks for the schema and plugins in the `Plugin` section.
# Conclusion
Overall, most of the conversion from Clockwork to Helix is simply renaming a certain function and/or switching the order of arguments around. Both are frameworks so they function similarly.
You may want to use our HL2 RP schema example for reference which can be found at [https://github.com/NebulousCloud/helix-hl2rp](https://github.com/NebulousCloud/helix-hl2rp)
================================================
FILE: docs/manual/getting-started.md
================================================
# Getting Started
It's pretty easy to get started with creating your own schema with Helix. It requires a bit of bootstrapping if you're starting from scratch, but you should quickly be on your way to developing your schema after following one of the below sections in this guide.
# Installing the framework
Before you start working on your schema, you'll need to install Helix onto your server. The exact instructions will vary based on your server provider, or if you're hosting the server yourself.
You'll need to download the framework from [GitHub](https://github.com/NebulousCloud/helix) into a folder called `helix`. This folder goes into your server's `gamemodes` folder at `garrysmod/gamemodes/helix`. That's it! The framework is now installed onto your server. Of course, you'll need to restart your server after installing the framework and a schema.
# MySQL usage
By default, Helix will use SQLite (which is built into Garry's Mod) to store player/character data. This requires no configuration and will work "out of the box" after installing and will be fine for most server owners. However, you might want to connect your database to your website or use multiple servers with one database - this will require the usage of an external database accessible elsewhere. This will require the use of a MySQL server. Some server providers will provide you with a MySQL database for free to use with your server.
## Installing
Helix uses the [MySQLOO](https://github.com/FredyH/MySQLOO) library to connect to MySQL databases. You'll need to follow the instructions for installing that library onto your server before continuing. In a nutshell, you need to make sure `gmsv_mysqloo_win32.dll` or `gmsv_mysqloo_linux.dll` (depending on your server's operating system) is in the `garrysmod/lua/bin` folder.
In older versions of MySQLOO, you previously required a .dll called `libmysql.dll` to place in your `root` server folder, where `srcds`/`srcds_linux` was stored. Newer versions of MySQLOO no longer require this.
## Configuring
Now that you've installed MySQLOO, you need to tell Helix that you want to connect to an external MySQL database instead of using SQLite. This requires creating a `helix.yml` configuration file in the `garrysmod/gamemodes/helix` folder. There is an example one already made for you called `helix.example.yml` that you can copy and rename to `helix.yml`.
The first thing you'll need to change is the `adapter` entry so that it says `mysqloo`. Next is to change the other entries to match your database's connection information. Here is an example of what your `helix.yml` should look like:
```
database:
adapter: "mysqloo"
hostname: "myexampledatabase.com"
username: "myusername"
password: "mypassword"
database: "helix"
port: 3306
```
The `hostname` field can either be a domain name (like `myexampledatabase.com`) or an IP address (`123.123.123.123`). If you don't know what the `port` field should be, simply leave it as the default `3306`; this is the default port for MySQL database connections. The `database` field is the name of the database that you've created for Helix. Note that it does not need to be `helix`, it can be whatever you'd like.
Another important thing to note about this configuration file is that you **must** indent with **two spaces only**. `database` should not have any spacing before it, and all other entries must have two spaces before them. Failing to ensure this will make the configuration file fail to load.
# Starting with the HL2 RP schema (Basic)
This section is for using the existing HL2 RP schema as a base for your own schema. It contains a good amount of example code if you need a stronger foundation than just a skeleton.
First, you'll need to download the schema from [GitHub](https://github.com/NebulousCloud/helix-hl2rp). Make sure that you download the contents of the repository into a folder called `ixhl2rp` and place it into your `garrysmod/gamemodes` folder. That's all you'll need to do to get the schema installed, other than setting your gamemode to `ixhl2rp` in the server's command line.
# Starting with the skeleton (Basic)
If you don't want excess code you might not use, or prefer to build from an almost-empty foundation that covers the basic bootstrapping, then the skeleton schema is for you. The skeleton schema contains a lot of comments explaining why code is laid out in a certain way, and some other helpful tips/explanations. Make sure you give it a read!
You'll need to download the schema from [GitHub](https://github.com/NebulousCloud/helix-skeleton) into the folder name of your choice - just make sure it's all lowercase with no spaces. Our example for the sake of brevity will be `myschema`. Place the folder into `garrysmod/gamemodes`.
Next up is to modify the gamemode info so that Garry's Mod will properly recognize it. Rename `skeleton.txt` in your schema folder to your folder's name. In our example we would rename `skeleton.txt` to `myschema.txt`. Next, you'll need to modify the contents of `myschema.txt` and replace the existing information with your own - making sure to replace the `"skeleton"` at the top of the file to your folder's name. In our case we would replace it with `"myschema"`. Once you've renamed the file, you're all good to go!
# Converting from Clockwork (Intermediate)
If you are looking to switch to Helix from Clockwork, you can follow the @{converting-from-clockwork|conversion guide}.
# Starting from scratch (Intermediate)
You can always create the gamemode files yourself if you'd like (although we suggest the skeleton schema in general). In general, a schema is a gamemode that is derived from `helix` and automatically loads `schema/sh_schema.lua`. You shouldn't have your schema files outside of the `schema` folder. The files you'll need are as follows:
`gamemode/init.lua`
```
AddCSLuaFile("cl_init.lua")
DeriveGamemode("helix")
```
`gamemode/cl_init.lua`
```
DeriveGamemode("helix")
```
`schema/sh_schema.lua`
```
Schema.name = "My Schema"
Schema.author = "me!"
Schema.description = "My awesome schema."
-- include your other schema files
ix.util.Include("cl_schema.lua")
ix.util.Include("sv_schema.lua")
-- etc.
```
================================================
FILE: docs/templates/landing.ltp
================================================
Helix Documentation
Welcome to the documentation for Helix - the better gamemode framework.
Developers
The sidebar shows the entire contents of the documentation. Libraries, functions, etc are all searchable with the search box at the top of the sidebar. Migrating from Clockwork? Check out the conversion guide in the manual.
Server owners
If you're looking to get your Helix server up and running as soon as possible, check out the Getting Started guide in the manual.
Community
Questions? Want to show off your work? Maybe drop a new plugin release? Come join our community Discord server.
Contributing
Helix is a large project and there are still a few things missing here and there. Contributions to the documentation - from function references, to simple typo fixes - are welcomed! Check out the ix.storage library's source code for a good example on how to write documentation. You'll need a basic understanding of Markdown, since it's used extensively to generate the markup.
If you'd like to contribute code, you can visit the GitHub repository and make a pull request.
Learning
Getting started on developing with the Helix framework requires an intermediate level of Garry's Mod Lua knowledge. You'll want to learn the basics before you get starting making a schema. The Garry's Mod Wiki is a good place to start.
================================================
FILE: docs/templates/ldoc.ltp
================================================
{%
local baseUrl = ldoc.css:gsub("ldoc.css", "")
local repo = "https://github.com/nebulouscloud/helix/"
local pageTitle = mod and (ldoc.display_name(mod) .. " - " .. ldoc.title) or ldoc.title
local oldmarkup = ldoc.markup
function ldoc.markup(text, item)
return oldmarkup(text, item, ldoc.plain)
end
function ldoc.url(path)
return baseUrl .. path
end
function ldoc.realm_icon(realm)
return "";
end
function ldoc.is_kind_classmethod(kind)
return kind ~= "libraries"
end
function ldoc.repo_reference(item)
return repo .. "tree/master" .. item.file.filename:gsub(item.file.base, "/gamemode") .. "#L" .. item.lineno
end
local function moduleDescription(mod)
if (mod.type == "topic") then
return mod.body:gsub(mod.display_name, ""):gsub("#", ""):sub(1, 256) .. "..."
end
return mod.summary
end
%}
{{pageTitle}}
{% if (mod) then %}
{% else %}
{% end %}
{(docs/templates/sidebar.ltp)}
{% if (ldoc.root) then -- we're rendering the landing page (index.html) %}
{(docs/templates/landing.ltp)}
{% elseif (ldoc.body) then -- we're rendering non-code elements %}
{* ldoc.body *}
{% elseif (module) then -- we're rendering libary contents %}
{(docs/templates/module.ltp)}
{% end %}
================================================
FILE: docs/templates/module.ltp
================================================
This is an internal function! You are able to use it, but you risk unintended side effects if used incorrectly.
{% end %}
{% if (item.module and item.module.type ~= "hooks") then %}
View source »
{% end %}
{% if (ldoc.descript(item):len() == 0) then %}
Incomplete
Documentation for this section is incomplete and needs expanding.
{% else %}
{* ldoc.markup(ldoc.descript(item)) *}
{% end %}
{# function arguments #}
{% if (item.params and #item.params > 0) then %}
{% local subnames = mod.kinds:type_of(item).subnames %}
{% if (subnames) then %}
{{subnames}}
{% end %}
{% for argument in ldoc.modules.iter(item.params) do %}
{% local argument, sublist = item:subparam(argument) %}
{% for argumentName in ldoc.modules.iter(argument) do %}
{% local displayName = item:display_name_of(argumentName) %}
{% local type = ldoc.typename(item:type_of_param(argumentName)) %}
{% local default = item:default_of_param(argumentName) %}
{{displayName}}
{% if (type ~= "") then %}
{* type *}
{% end %}
{% if (default and default ~= true) then %}
default: {{default}}
{% elseif (default) then %}
optional
{% end %}
{* ldoc.markup(item.params.map[argumentName]) *}
{% end %}
{% end %}
{% end %}
{# function returns #}
{% if ((not ldoc.no_return_or_parms) and item.retgroups) then %}
{% local groups = item.retgroups %}
Returns
{% for i, group in ldoc.ipairs(groups) do %}
{% for returnValue in group:iter() do %}
{% local type, ctypes = item:return_type(returnValue) %}
{% type = ldoc.typename(type) %}
{% if (type ~= "") then %}
{* type *}
{% else -- we'll assume that it will return a variable type if none is set %}
any
{% end %}
{* ldoc.markup(returnValue.text) *}
{% end %}
{% if (i ~= #groups) then %}
OR
{% end %}
{% end %}
{% end %}
{% if (item.usage) then -- function usage %}
Example Usage
{% for usage in ldoc.modules.iter(item.usage) do %}
{% end %}
{% end %}
{% end %}
================================================
FILE: docs/templates/sidebar.ltp
================================================
{%
local function isKindExpandable(kind)
return kind ~= "Manual"
end
%}
================================================
FILE: entities/entities/ix_item.lua
================================================
AddCSLuaFile()
ENT.Base = "base_entity"
ENT.Type = "anim"
ENT.PrintName = "Item"
ENT.Category = "Helix"
ENT.Spawnable = false
ENT.ShowPlayerInteraction = true
ENT.RenderGroup = RENDERGROUP_BOTH
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("String", 0, "ItemID")
end
if (SERVER) then
local invalidBoundsMin = Vector(-8, -8, -8)
local invalidBoundsMax = Vector(8, 8, 8)
util.AddNetworkString("ixItemEntityAction")
function ENT:Initialize()
self:SetModel("models/props_junk/watermelon01.mdl")
self:SetSolid(SOLID_VPHYSICS)
self:PhysicsInit(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self.health = 50
local physObj = self:GetPhysicsObject()
if (IsValid(physObj)) then
physObj:EnableMotion(true)
physObj:Wake()
end
end
function ENT:Use(activator, caller)
local itemTable = self:GetItemTable()
if (IsValid(caller) and caller:IsPlayer() and caller:GetCharacter() and itemTable) then
itemTable.player = caller
itemTable.entity = self
if (itemTable.functions.take.OnCanRun(itemTable)) then
caller:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client)
if (!ix.item.PerformInventoryAction(client, "take", self)) then
return false -- do not mark dirty if interaction fails
end
end)
end
itemTable.player = nil
itemTable.entity = nil
end
end
function ENT:SetItem(itemID)
local itemTable = ix.item.instances[itemID]
if (itemTable) then
local material = itemTable:GetMaterial(self)
self:SetSkin(itemTable:GetSkin())
self:SetModel(itemTable:GetModel())
if (material) then
self:SetMaterial(material)
end
self:PhysicsInit(SOLID_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetItemID(itemTable.uniqueID)
self.ixItemID = itemID
if (!table.IsEmpty(itemTable.data)) then
self:SetNetVar("data", itemTable.data)
end
local physObj = self:GetPhysicsObject()
if (!IsValid(physObj)) then
self:PhysicsInitBox(invalidBoundsMin, invalidBoundsMax)
self:SetCollisionBounds(invalidBoundsMin, invalidBoundsMax)
end
if (IsValid(physObj)) then
physObj:EnableMotion(true)
physObj:Wake()
end
if (itemTable.OnEntityCreated) then
itemTable:OnEntityCreated(self)
end
end
end
function ENT:OnDuplicated(entTable)
local itemID = entTable.ixItemID
local itemTable = ix.item.instances[itemID]
ix.item.Instance(0, itemTable.uniqueID, itemTable.data, 1, 1, function(item)
self:SetItem(item:GetID())
end)
end
function ENT:OnTakeDamage(damageInfo)
local itemTable = ix.item.instances[self.ixItemID]
if (itemTable.OnEntityTakeDamage
and itemTable:OnEntityTakeDamage(self, damageInfo) == false) then
return
end
local damage = damageInfo:GetDamage()
self:SetHealth(self:Health() - damage)
if (self:Health() <= 0 and !self.ixIsDestroying) then
self.ixIsDestroying = true
self.ixDamageInfo = {damageInfo:GetAttacker(), damage, damageInfo:GetInflictor()}
self:Remove()
end
end
function ENT:OnRemove()
if (!ix.shuttingDown and !self.ixIsSafe and self.ixItemID) then
local itemTable = ix.item.instances[self.ixItemID]
if (itemTable) then
if (self.ixIsDestroying) then
self:EmitSound("physics/cardboard/cardboard_box_break"..math.random(1, 3)..".wav")
local position = self:LocalToWorld(self:OBBCenter())
local effect = EffectData()
effect:SetStart(position)
effect:SetOrigin(position)
effect:SetScale(3)
util.Effect("GlassImpact", effect)
if (itemTable.OnDestroyed) then
itemTable:OnDestroyed(self)
end
ix.log.Add(self.ixDamageInfo[1], "itemDestroy", itemTable:GetName(), itemTable:GetID())
end
if (itemTable.OnRemoved) then
itemTable:OnRemoved()
end
local query = mysql:Delete("ix_items")
query:Where("item_id", self.ixItemID)
query:Execute()
end
end
end
function ENT:Think()
local itemTable = self:GetItemTable()
if (!itemTable) then
self:Remove()
end
if (itemTable.Think) then
itemTable:Think(self)
end
return true
end
function ENT:UpdateTransmitState()
return TRANSMIT_PVS
end
net.Receive("ixItemEntityAction", function(length, client)
ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadEntity())
end)
else
ENT.PopulateEntityInfo = true
local shadeColor = Color(0, 0, 0, 200)
local blockSize = 4
local blockSpacing = 2
function ENT:OnPopulateEntityInfo(tooltip)
local item = self:GetItemTable()
if (!item) then
return
end
local oldData = item.data
item.data = self:GetNetVar("data", {})
item.entity = self
ix.hud.PopulateItemTooltip(tooltip, item)
local name = tooltip:GetRow("name")
local color = name and name:GetBackgroundColor() or ix.config.Get("color")
-- set the arrow to be the same colour as the title/name row
tooltip:SetArrowColor(color)
if ((item.width > 1 or item.height > 1) and
hook.Run("ShouldDrawItemSize", item) != false) then
local sizeHeight = item.height * blockSize + item.height * blockSpacing
local size = tooltip:Add("Panel")
size:SetWide(tooltip:GetWide())
if (tooltip:IsMinimal()) then
size:SetTall(sizeHeight)
size:Dock(TOP)
size:SetZPos(-999)
else
size:SetTall(sizeHeight + 8)
size:Dock(BOTTOM)
end
size.Paint = function(sizePanel, width, height)
if (!tooltip:IsMinimal()) then
surface.SetDrawColor(ColorAlpha(shadeColor, 60))
surface.DrawRect(0, 0, width, height)
end
local x, y = width * 0.5 - 1, height * 0.5 - 1
local itemWidth = item.width - 1
local itemHeight = item.height - 1
local heightDifference = ((itemHeight + 1) * blockSize + blockSpacing * itemHeight)
x = x - (itemWidth * blockSize + blockSpacing * itemWidth) * 0.5
y = y - heightDifference * 0.5
for i = 0, itemHeight do
for j = 0, itemWidth do
local blockX, blockY = x + j * blockSize + j * blockSpacing, y + i * blockSize + i * blockSpacing
surface.SetDrawColor(shadeColor)
surface.DrawRect(blockX + 1, blockY + 1, blockSize, blockSize)
surface.SetDrawColor(color)
surface.DrawRect(blockX, blockY, blockSize, blockSize)
end
end
end
tooltip:SizeToContents()
end
item.entity = nil
item.data = oldData
end
function ENT:DrawTranslucent()
local itemTable = self:GetItemTable()
if (itemTable and itemTable.DrawEntity) then
itemTable:DrawEntity(self)
end
end
function ENT:Draw()
self:DrawModel()
end
end
function ENT:GetEntityMenu(client)
local itemTable = self:GetItemTable()
local options = {}
if (!itemTable) then
return false
end
itemTable.player = client
itemTable.entity = self
for k, v in SortedPairs(itemTable.functions) do
if (k == "take" or k == "combine") then
continue
end
if (v.OnCanRun and v.OnCanRun(itemTable) == false) then
continue
end
-- we keep the localized phrase since we aren't using the callbacks - the name won't matter in this case
options[L(v.name or k)] = function()
local send = true
if (v.OnClick) then
send = v.OnClick(itemTable)
end
if (v.sound) then
surface.PlaySound(v.sound)
end
if (send != false) then
net.Start("ixItemEntityAction")
net.WriteString(k)
net.WriteEntity(self)
net.SendToServer()
end
-- don't run callbacks since we're handling it manually
return false
end
end
itemTable.player = nil
itemTable.entity = nil
return options
end
function ENT:GetItemTable()
return ix.item.list[self:GetItemID()]
end
function ENT:GetData(key, default)
local data = self:GetNetVar("data", {})
return data[key] or default
end
================================================
FILE: entities/entities/ix_money.lua
================================================
AddCSLuaFile()
ENT.Type = "anim"
ENT.PrintName = "Money"
ENT.Category = "Helix"
ENT.Spawnable = false
ENT.ShowPlayerInteraction = true
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("Int", 0, "Amount")
end
if (SERVER) then
local invalidBoundsMin = Vector(-8, -8, -8)
local invalidBoundsMax = Vector(8, 8, 8)
function ENT:Initialize()
self:SetModel(ix.currency.model)
self:SetSolid(SOLID_VPHYSICS)
self:PhysicsInit(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
local physObj = self:GetPhysicsObject()
if (IsValid(physObj)) then
physObj:EnableMotion(true)
physObj:Wake()
else
self:PhysicsInitBox(invalidBoundsMin, invalidBoundsMax)
self:SetCollisionBounds(invalidBoundsMin, invalidBoundsMax)
end
end
function ENT:Use(activator)
if (self.ixSteamID and self.ixCharID) then
local char = activator:GetCharacter()
if (char and self.ixCharID != char:GetID() and self.ixSteamID == activator:SteamID()) then
activator:NotifyLocalized("itemOwned")
return false
end
end
activator:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client)
if (hook.Run("OnPickupMoney", client, self) != false) then
self:Remove()
end
end)
end
function ENT:UpdateTransmitState()
return TRANSMIT_PVS
end
else
ENT.PopulateEntityInfo = true
function ENT:OnPopulateEntityInfo(container)
local text = container:AddRow("name")
text:SetImportant()
text:SetText(ix.currency.Get(self:GetAmount()))
text:SizeToContents()
end
end
================================================
FILE: entities/entities/ix_shipment.lua
================================================
AddCSLuaFile()
ENT.Type = "anim"
ENT.PrintName = "Shipment"
ENT.Category = "Helix"
ENT.Spawnable = false
ENT.ShowPlayerInteraction = true
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("Int", 0, "DeliveryTime")
end
if (SERVER) then
function ENT:Initialize()
self:SetModel("models/Items/item_item_crate.mdl")
self:SetSolid(SOLID_VPHYSICS)
self:PhysicsInit(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self:PrecacheGibs()
local physObj = self:GetPhysicsObject()
if (IsValid(physObj)) then
physObj:EnableMotion(true)
physObj:Wake()
end
self:SetDeliveryTime(CurTime() + 120)
timer.Simple(120, function()
if (IsValid(self)) then
self:Remove()
end
end)
end
function ENT:Use(activator)
activator:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client)
if (client:GetCharacter() and client:GetCharacter():GetID() == self:GetNetVar("owner", 0)
and hook.Run("CanPlayerOpenShipment", client, self) != false) then
client.ixShipment = self
net.Start("ixShipmentOpen")
net.WriteEntity(self)
net.WriteTable(self.items)
net.Send(client)
end
-- don't mark dirty since the player could come back and use this shipment again later
return false
end)
end
function ENT:SetItems(items)
self.items = items
end
function ENT:GetItemCount()
local count = 0
for _, v in pairs(self.items) do
count = count + math.max(v, 0)
end
return count
end
function ENT:OnRemove()
self:EmitSound("physics/cardboard/cardboard_box_break"..math.random(1, 3)..".wav")
local position = self:LocalToWorld(self:OBBCenter())
local effect = EffectData()
effect:SetStart(position)
effect:SetOrigin(position)
effect:SetScale(3)
util.Effect("GlassImpact", effect)
end
function ENT:UpdateTransmitState()
return TRANSMIT_PVS
end
else
ENT.PopulateEntityInfo = true
local size = 150
local tempMat = Material("particle/warp1_warp", "alphatest")
function ENT:Draw()
local pos, ang = self:GetPos(), self:GetAngles()
self:DrawModel()
pos = pos + self:GetUp() * 25
pos = pos + self:GetForward() * 1
pos = pos + self:GetRight() * 3
local delTime = math.max(math.ceil(self:GetDeliveryTime() - CurTime()), 0)
local func = function()
surface.SetMaterial(tempMat)
surface.SetDrawColor(0, 0, 0, 200)
surface.DrawTexturedRect(-size / 2, -size / 2 - 10, size, size)
ix.util.DrawText("k", 0, 0, color_white, 1, 4, "ixIconsBig")
ix.util.DrawText(delTime, 0, -10, color_white, 1, 5, "ixBigFont")
end
cam.Start3D2D(pos, ang, .15)
func()
cam.End3D2D()
ang:RotateAroundAxis(ang:Right(), 180)
pos = pos - self:GetUp() * 26
cam.Start3D2D(pos, ang, .15)
func()
cam.End3D2D()
end
function ENT:OnPopulateEntityInfo(container)
local owner = ix.char.loaded[self:GetNetVar("owner", 0)]
local name = container:AddRow("name")
name:SetImportant()
name:SetText(L("shipment"))
name:SizeToContents()
if (owner) then
local description = container:AddRow("description")
description:SetText(L("shipmentDesc", owner:GetName()))
description:SizeToContents()
end
end
end
================================================
FILE: entities/weapons/ix_hands.lua
================================================
AddCSLuaFile()
if (CLIENT) then
SWEP.PrintName = "Hands"
SWEP.Slot = 0
SWEP.SlotPos = 1
SWEP.DrawAmmo = false
SWEP.DrawCrosshair = true
end
SWEP.Author = "Chessnut"
SWEP.Instructions = [[Primary Fire: Throw/Punch
Secondary Fire: Knock/Pickup
Secondary Fire + Mouse: Rotate Object
Reload: Drop]]
SWEP.Purpose = "Hitting things and knocking on doors."
SWEP.Drop = false
SWEP.ViewModelFOV = 45
SWEP.ViewModelFlip = false
SWEP.AnimPrefix = "rpg"
SWEP.ViewTranslation = 4
if CLIENT then
SWEP.NextAllowedPlayRateChange = 0
end
SWEP.Primary.ClipSize = -1
SWEP.Primary.DefaultClip = -1
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = ""
SWEP.Primary.Damage = 5
SWEP.Primary.Delay = 0.75
SWEP.Secondary.ClipSize = -1
SWEP.Secondary.DefaultClip = 0
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = ""
SWEP.Secondary.Delay = 0.5
SWEP.ViewModel = Model("models/weapons/c_arms.mdl")
SWEP.WorldModel = ""
SWEP.UseHands = true
SWEP.LowerAngles = Angle(0, 5, -14)
SWEP.LowerAngles2 = Angle(0, 5, -19)
SWEP.KnockViewPunchAngle = Angle(-1.3, 1.8, 0)
SWEP.FireWhenLowered = true
SWEP.HoldType = "fist"
SWEP.holdDistance = 64
SWEP.maxHoldDistance = 96 -- how far away the held object is allowed to travel before forcefully dropping
SWEP.maxHoldStress = 4000 -- how much stress the held object can undergo before forcefully dropping
-- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER
ACT_VM_FISTS_DRAW = 2
ACT_VM_FISTS_HOLSTER = 1
function SWEP:Initialize()
self:SetHoldType(self.HoldType)
self.lastHand = 0
self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2
self.heldObjectAngle = Angle(angle_zero)
end
if (CLIENT) then
function SWEP:DoDrawCrosshair(x, y)
surface.SetDrawColor(255, 255, 255, 66)
surface.DrawRect(x - 2, y - 2, 4, 4)
end
hook.Add("CreateMove", "ixHandsCreateMove", function(cmd)
if (LocalPlayer():GetLocalVar("bIsHoldingObject", false) and cmd:KeyDown(IN_ATTACK2)) then
cmd:ClearMovement()
local angle = RenderAngles()
angle.z = 0
cmd:SetViewAngles(angle)
end
end)
end
function SWEP:Deploy()
if (!IsValid(self:GetOwner())) then
return
end
local viewModel = self:GetOwner():GetViewModel()
if (IsValid(viewModel)) then
viewModel:SetPlaybackRate(1)
viewModel:ResetSequence(ACT_VM_FISTS_DRAW)
if CLIENT then
self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration()
end
end
self:DropObject()
return true
end
function SWEP:Precache()
util.PrecacheSound("npc/vort/claw_swing1.wav")
util.PrecacheSound("npc/vort/claw_swing2.wav")
util.PrecacheSound("physics/plastic/plastic_box_impact_hard1.wav")
util.PrecacheSound("physics/plastic/plastic_box_impact_hard2.wav")
util.PrecacheSound("physics/plastic/plastic_box_impact_hard3.wav")
util.PrecacheSound("physics/plastic/plastic_box_impact_hard4.wav")
util.PrecacheSound("physics/wood/wood_crate_impact_hard2.wav")
util.PrecacheSound("physics/wood/wood_crate_impact_hard3.wav")
end
function SWEP:OnReloaded()
self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2
self:DropObject()
end
function SWEP:Holster()
if (!IsValid(self:GetOwner())) then
return
end
local viewModel = self:GetOwner():GetViewModel()
if (IsValid(viewModel)) then
viewModel:SetPlaybackRate(1)
viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER)
if CLIENT then
self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration()
end
end
return true
end
function SWEP:Think()
if (!IsValid(self:GetOwner())) then
return
end
if (CLIENT) then
local viewModel = self:GetOwner():GetViewModel()
if (IsValid(viewModel) and self.NextAllowedPlayRateChange < CurTime()) then
viewModel:SetPlaybackRate(1)
end
else
if (self:IsHoldingObject()) then
local physics = self:GetHeldPhysicsObject()
local bIsRagdoll = self.heldEntity:IsRagdoll()
local holdDistance = bIsRagdoll and self.holdDistance * 0.5 or self.holdDistance
local targetLocation = self:GetOwner():GetShootPos() + self:GetOwner():GetForward() * holdDistance
if (bIsRagdoll) then
targetLocation.z = math.min(targetLocation.z, self:GetOwner():GetShootPos().z - 32)
end
if (!IsValid(physics)) then
self:DropObject()
return
end
if (physics:GetPos():DistToSqr(targetLocation) > self.maxHoldDistanceSquared) then
self:DropObject()
else
local physicsObject = self.holdEntity:GetPhysicsObject()
local currentPlayerAngles = self:GetOwner():EyeAngles()
local client = self:GetOwner()
if (client:KeyDown(IN_ATTACK2)) then
local cmd = client:GetCurrentCommand()
self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Forward(), cmd:GetMouseX() / 15)
self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Right(), cmd:GetMouseY() / 15)
end
self.lastPlayerAngles = self.lastPlayerAngles or currentPlayerAngles
self.heldObjectAngle.y = self.heldObjectAngle.y - math.AngleDifference(self.lastPlayerAngles.y, currentPlayerAngles.y)
self.lastPlayerAngles = currentPlayerAngles
physicsObject:Wake()
physicsObject:ComputeShadowControl({
secondstoarrive = 0.01,
pos = targetLocation,
angle = self.heldObjectAngle,
maxangular = 256,
maxangulardamp = 10000,
maxspeed = 256,
maxspeeddamp = 10000,
dampfactor = 0.8,
teleportdistance = self.maxHoldDistance * 0.75,
deltatime = FrameTime()
})
if (physics:GetStress() > self.maxHoldStress) then
self:DropObject()
end
end
end
-- Prevents the camera from getting stuck when the object that the client is holding gets deleted.
if(!IsValid(self.heldEntity) and self:GetOwner():GetLocalVar("bIsHoldingObject", true)) then
self:GetOwner():SetLocalVar("bIsHoldingObject", false)
end
end
end
function SWEP:GetHeldPhysicsObject()
return IsValid(self.heldEntity) and self.heldEntity:GetPhysicsObject() or nil
end
function SWEP:CanHoldObject(entity)
local physics = entity:GetPhysicsObject()
return IsValid(physics) and
(physics:GetMass() <= ix.config.Get("maxHoldWeight", 100) and physics:IsMoveable()) and
!self:IsHoldingObject() and
!IsValid(entity.ixHeldOwner) and
hook.Run("CanPlayerHoldObject", self:GetOwner(), entity)
end
function SWEP:IsHoldingObject()
return IsValid(self.heldEntity) and
IsValid(self.heldEntity.ixHeldOwner) and
self.heldEntity.ixHeldOwner == self:GetOwner()
end
function SWEP:PickupObject(entity)
if (self:IsHoldingObject() or
!IsValid(entity) or
!IsValid(entity:GetPhysicsObject())) then
return
end
local physics = entity:GetPhysicsObject()
physics:EnableGravity(false)
physics:AddGameFlag(FVPHYSICS_PLAYER_HELD)
entity.ixHeldOwner = self:GetOwner()
entity.ixCollisionGroup = entity:GetCollisionGroup()
entity:StartMotionController()
entity:SetCollisionGroup(COLLISION_GROUP_WEAPON)
self.heldObjectAngle = entity:GetAngles()
self.heldEntity = entity
self.holdEntity = ents.Create("prop_physics")
self.holdEntity:SetPos(self.heldEntity:LocalToWorld(self.heldEntity:OBBCenter()))
self.holdEntity:SetAngles(self.heldEntity:GetAngles())
self.holdEntity:SetModel("models/weapons/w_bugbait.mdl")
self.holdEntity:SetOwner(self:GetOwner())
self.holdEntity:SetNoDraw(true)
self.holdEntity:SetNotSolid(true)
self.holdEntity:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
self.holdEntity:DrawShadow(false)
self.holdEntity:Spawn()
local trace = self:GetOwner():GetEyeTrace()
local physicsObject = self.holdEntity:GetPhysicsObject()
if (IsValid(physicsObject)) then
physicsObject:SetMass(2048)
physicsObject:SetDamping(0, 1000)
physicsObject:EnableGravity(false)
physicsObject:EnableCollisions(false)
physicsObject:EnableMotion(false)
end
if (trace.Entity:IsRagdoll()) then
local tracedEnt = trace.Entity
self.holdEntity:SetPos(tracedEnt:GetBonePosition(tracedEnt:TranslatePhysBoneToBone(trace.PhysicsBone)))
end
self.constraint = constraint.Weld(self.holdEntity, self.heldEntity, 0,
trace.Entity:IsRagdoll() and trace.PhysicsBone or 0, 0, true, true)
end
function SWEP:DropObject(bThrow)
if (!IsValid(self.heldEntity) or self.heldEntity.ixHeldOwner != self:GetOwner()) then
return
end
self.lastPlayerAngles = nil
self:GetOwner():SetLocalVar("bIsHoldingObject", false)
self.constraint:Remove()
self.holdEntity:Remove()
self.heldEntity:StopMotionController()
self.heldEntity:SetCollisionGroup(self.heldEntity.ixCollisionGroup or COLLISION_GROUP_NONE)
local physics = self:GetHeldPhysicsObject()
physics:EnableGravity(true)
physics:Wake()
physics:ClearGameFlag(FVPHYSICS_PLAYER_HELD)
if (bThrow) then
timer.Simple(0, function()
if (IsValid(physics) and IsValid(self:GetOwner())) then
physics:AddGameFlag(FVPHYSICS_WAS_THROWN)
physics:ApplyForceCenter(self:GetOwner():GetAimVector() * ix.config.Get("throwForce", 732))
end
end)
end
self.heldEntity.ixHeldOwner = nil
self.heldEntity.ixCollisionGroup = nil
self.heldEntity = nil
end
function SWEP:PlayPickupSound(surfaceProperty)
local result = "Flesh.ImpactSoft"
if (surfaceProperty != nil) then
local surfaceName = util.GetSurfacePropName(surfaceProperty)
local soundName = surfaceName:gsub("^metal$", "SolidMetal") .. ".ImpactSoft"
if (sound.GetProperties(soundName)) then
result = soundName
end
end
self:GetOwner():EmitSound(result, 75, 100, 40)
end
function SWEP:Holster()
if (!IsFirstTimePredicted() or CLIENT) then
return
end
self:DropObject()
return true
end
function SWEP:OnRemove()
if (SERVER) then
self:DropObject()
end
end
function SWEP:OwnerChanged()
if (SERVER) then
self:DropObject()
end
end
function SWEP:DoPunchAnimation()
self.lastHand = math.abs(1 - self.lastHand)
local sequence = 3 + self.lastHand
local viewModel = self:GetOwner():GetViewModel()
if (IsValid(viewModel)) then
viewModel:SetPlaybackRate(0.5)
viewModel:SetSequence(sequence)
if CLIENT then
self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2
end
end
end
function SWEP:PrimaryAttack()
if (!IsFirstTimePredicted()) then
return
end
if (SERVER and self:IsHoldingObject()) then
self:DropObject(true)
return
end
self:SetNextPrimaryFire(CurTime() + self.Primary.Delay)
if (hook.Run("CanPlayerThrowPunch", self:GetOwner()) == false) then
return
end
if (ix.plugin.Get("stamina")) then
local staminaUse = ix.config.Get("punchStamina")
if (staminaUse > 0) then
local value = self:GetOwner():GetLocalVar("stm", 0) - staminaUse
if (value < 0) then
return
elseif (SERVER) then
self:GetOwner():ConsumeStamina(staminaUse)
end
end
end
if (SERVER) then
self:GetOwner():EmitSound("npc/vort/claw_swing"..math.random(1, 2)..".wav")
end
self:DoPunchAnimation()
self:GetOwner():SetAnimation(PLAYER_ATTACK1)
self:GetOwner():ViewPunch(Angle(self.lastHand + 2, self.lastHand + 5, 0.125))
timer.Simple(0.055, function()
if (IsValid(self) and IsValid(self:GetOwner())) then
local damage = self.Primary.Damage
local context = {damage = damage}
local result = hook.Run("GetPlayerPunchDamage", self:GetOwner(), damage, context)
if (result != nil) then
damage = result
else
damage = context.damage
end
self:GetOwner():LagCompensation(true)
local data = {}
data.start = self:GetOwner():GetShootPos()
data.endpos = data.start + self:GetOwner():GetAimVector() * 96
data.filter = self:GetOwner()
local trace = util.TraceLine(data)
if (SERVER and trace.Hit) then
local entity = trace.Entity
if (IsValid(entity)) then
local damageInfo = DamageInfo()
damageInfo:SetAttacker(self:GetOwner())
damageInfo:SetInflictor(self)
damageInfo:SetDamage(damage)
damageInfo:SetDamageType(DMG_GENERIC)
damageInfo:SetDamagePosition(trace.HitPos)
damageInfo:SetDamageForce(self:GetOwner():GetAimVector() * 1024)
entity:DispatchTraceAttack(damageInfo, data.start, data.endpos)
self:GetOwner():EmitSound("physics/body/body_medium_impact_hard"..math.random(1, 6)..".wav", 80)
end
end
hook.Run("PlayerThrowPunch", self:GetOwner(), trace)
self:GetOwner():LagCompensation(false)
end
end)
end
function SWEP:SecondaryAttack()
if (!IsFirstTimePredicted()) then
return
end
local data = {}
data.start = self:GetOwner():GetShootPos()
data.endpos = data.start + self:GetOwner():GetAimVector() * 84
data.filter = {self, self:GetOwner()}
local trace = util.TraceLine(data)
local entity = trace.Entity
if CLIENT then
local viewModel = self:GetOwner():GetViewModel()
if (IsValid(viewModel)) then
viewModel:SetPlaybackRate(0.5)
if CLIENT then
self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2
end
end
end
if (SERVER and IsValid(entity)) then
if (entity:IsDoor()) then
if (hook.Run("CanPlayerKnock", self:GetOwner(), entity) == false) then
return
end
self:GetOwner():ViewPunch(self.KnockViewPunchAngle)
self:GetOwner():EmitSound("physics/wood/wood_crate_impact_hard"..math.random(2, 3)..".wav")
self:GetOwner():SetAnimation(PLAYER_ATTACK1)
self:DoPunchAnimation()
self:SetNextSecondaryFire(CurTime() + 0.4)
self:SetNextPrimaryFire(CurTime() + 1)
elseif (entity:IsPlayer() and ix.config.Get("allowPush", true)) then
local direction = self:GetOwner():GetAimVector() * (300 + (self:GetOwner():GetCharacter():GetAttribute("str", 0) * 3))
direction.z = 0
entity:SetVelocity(direction)
self:GetOwner():EmitSound("Weapon_Crossbow.BoltHitBody")
self:SetNextSecondaryFire(CurTime() + 1.5)
self:SetNextPrimaryFire(CurTime() + 1.5)
elseif (!entity:IsNPC() and self:CanHoldObject(entity)) then
self:GetOwner():SetLocalVar("bIsHoldingObject", true)
self:PickupObject(entity)
self:PlayPickupSound(trace.SurfaceProps)
self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay)
end
end
end
function SWEP:Reload()
if (!IsFirstTimePredicted()) then
return
end
if (SERVER and IsValid(self.heldEntity)) then
self:DropObject()
end
end
================================================
FILE: gamemode/cl_init.lua
================================================
-- unix systems are case-sensitive, are missing fonts, or use different naming conventions
if (!system.IsWindows()) then
local fontOverrides = {
["Roboto"] = "Roboto Regular",
["Roboto Th"] = "Roboto Thin",
["Roboto Lt"] = "Roboto Light",
["Roboto Bk"] = "Roboto Black",
["coolvetica"] = "Coolvetica",
["tahoma"] = "Tahoma",
["Harmonia Sans Pro Cyr"] = "Roboto Regular",
["Harmonia Sans Pro Cyr Light"] = "Roboto Light",
["Century Gothic"] = "Roboto Regular"
}
if (system.IsOSX()) then
fontOverrides["Consolas"] = "Monaco"
else
fontOverrides["Consolas"] = "Courier New"
end
local ixCreateFont = surface.CreateFont
function surface.CreateFont(name, info) -- luacheck: globals surface
local font = info.font
if (font and fontOverrides[font]) then
info.font = fontOverrides[font]
end
ixCreateFont(name, info)
end
end
DeriveGamemode("sandbox")
ix = ix or {util = {}, gui = {}, meta = {}}
-- Include core files.
include("core/sh_util.lua")
include("core/sh_data.lua")
include("shared.lua")
-- Sandbox stuff
CreateConVar("cl_weaponcolor", "0.30 1.80 2.10", {
FCVAR_ARCHIVE, FCVAR_USERINFO, FCVAR_DONTRECORD
}, "The value is a Vector - so between 0-1 - not between 0-255")
timer.Remove("HintSystem_OpeningMenu")
timer.Remove("HintSystem_Annoy1")
timer.Remove("HintSystem_Annoy2")
================================================
FILE: gamemode/config/sh_config.lua
================================================
-- You can change the default language by setting this in your schema.
ix.config.language = "english"
--[[
DO NOT CHANGE ANYTHING BELOW THIS.
This is the Helix main configuration file.
This file DOES NOT set any configurations, instead it just prepares them.
To set the configuration, there is a "Config" tab in the F1 menu for super admins and above.
Use the menu to change the variables, not this file.
--]]
ix.config.Add("maxCharacters", 5, "The maximum number of characters a player can have.", nil, {
data = {min = 1, max = 50},
category = "characters"
})
ix.config.Add("color", Color(75, 119, 190, 255), "The main color theme for the framework.", function(oldValue, newValue)
if (newValue.a != 255) then
ix.config.Set("color", ColorAlpha(newValue, 255))
return
end
if (CLIENT) then
hook.Run("ColorSchemeChanged", newValue)
end
end, {category = "appearance"})
ix.config.Add("font", "Roboto Th", "The font used to display titles.", function(oldValue, newValue)
if (CLIENT) then
hook.Run("LoadFonts", newValue, ix.config.Get("genericFont"))
end
end, {category = "appearance"})
ix.config.Add("genericFont", "Roboto", "The font used to display generic texts.", function(oldValue, newValue)
if (CLIENT) then
hook.Run("LoadFonts", ix.config.Get("font"), newValue)
end
end, {category = "appearance"})
ix.config.Add("maxAttributes", 100, "The maximum amount each attribute can be.", nil, {
data = {min = 0, max = 100},
category = "characters"
})
ix.config.Add("chatAutoFormat", true, "Whether or not to automatically capitalize and punctuate in-character text.", nil, {
category = "Chat"
})
ix.config.Add("chatRange", 280, "The maximum distance a person's IC chat message goes to.", nil, {
data = {min = 10, max = 5000, decimals = 1},
category = "chat"
})
ix.config.Add("chatMax", 256, "The maximum amount of characters that can be sent in chat.", nil, {
data = {min = 32, max = 1024},
category = "chat"
})
ix.config.Add("chatColor", Color(255, 255, 150), "The default color for IC chat.", nil, {category = "chat"})
ix.config.Add("chatListenColor", Color(175, 255, 150), "The color for IC chat if you are looking at the speaker.", nil, {
category = "chat"
})
ix.config.Add("oocDelay", 10, "The delay before a player can use OOC chat again in seconds.", nil, {
data = {min = 0, max = 10000},
category = "chat"
})
ix.config.Add("allowGlobalOOC", true, "Whether or not Global OOC is enabled.", nil, {
category = "chat"
})
ix.config.Add("loocDelay", 0, "The delay before a player can use LOOC chat again in seconds.", nil, {
data = {min = 0, max = 10000},
category = "chat"
})
ix.config.Add("spawnTime", 5, "The time it takes to respawn.", nil, {
data = {min = 0, max = 10000},
category = "characters"
})
ix.config.Add("inventoryWidth", 6, "How many slots in a row there is in a default inventory.", nil, {
data = {min = 0, max = 20},
category = "characters"
})
ix.config.Add("inventoryHeight", 4, "How many slots in a column there is in a default inventory.", nil, {
data = {min = 0, max = 20},
category = "characters"
})
ix.config.Add("minNameLength", 4, "The minimum number of characters in a name.", nil, {
data = {min = 4, max = 64},
category = "characters"
})
ix.config.Add("maxNameLength", 32, "The maximum number of characters in a name.", nil, {
data = {min = 16, max = 128},
category = "characters"
})
ix.config.Add("minDescriptionLength", 16, "The minimum number of characters in a description.", nil, {
data = {min = 0, max = 300},
category = "characters"
})
ix.config.Add("saveInterval", 300, "How often characters save in seconds.", nil, {
data = {min = 60, max = 3600},
category = "characters"
})
ix.config.Add("walkSpeed", 130, "How fast a player normally walks.", function(oldValue, newValue)
for _, v in player.Iterator() do
v:SetWalkSpeed(newValue)
end
end, {
data = {min = 75, max = 500},
category = "characters"
})
ix.config.Add("runSpeed", 235, "How fast a player normally runs.", function(oldValue, newValue)
for _, v in player.Iterator() do
v:SetRunSpeed(newValue)
end
end, {
data = {min = 75, max = 500},
category = "characters"
})
ix.config.Add("walkRatio", 0.5, "How fast one goes when holding ALT.", nil, {
data = {min = 0, max = 1, decimals = 1},
category = "characters"
})
ix.config.Add("intro", true, "Whether or not the Helix intro is enabled for new players.", nil, {
category = "appearance"
})
ix.config.Add("music", "music/hl2_song2.mp3", "The default music played in the character menu.", nil, {
category = "appearance"
})
ix.config.Add("communityURL", "https://nebulous.cloud/", "The URL to navigate to when the community button is clicked.", nil, {
category = "appearance"
})
ix.config.Add("communityText", "@community",
"The text to display on the community button. You can use language phrases by prefixing with @", nil, {
category = "appearance"
})
ix.config.Add("vignette", true, "Whether or not the vignette is shown.", nil, {
category = "appearance"
})
ix.config.Add("scoreboardRecognition", false, "Whether or not recognition is used in the scoreboard.", nil, {
category = "characters"
})
ix.config.Add("defaultMoney", 0, "The amount of money that players start with.", nil, {
category = "characters",
data = {min = 0, max = 1000}
})
ix.config.Add("minMoneyDropAmount", 1, "The minimum amount of money that can be dropped.", nil, {
category = "characters",
data = {min = 1, max = 1000}
})
ix.config.Add("allowVoice", false, "Whether or not voice chat is allowed.", function(oldValue, newValue)
if (SERVER) then
hook.Run("VoiceToggled", newValue)
end
end, {
category = "server"
})
ix.config.Add("voiceDistance", 600.0, "How far can the voice be heard.", function(oldValue, newValue)
if (SERVER) then
hook.Run("VoiceDistanceChanged", newValue)
end
end, {
category = "server",
data = {min = 0, max = 5000, decimals = 1}
})
ix.config.Add("weaponAlwaysRaised", false, "Whether or not weapons are always raised.", nil, {
category = "server"
})
ix.config.Add("weaponRaiseTime", 1, "The time it takes for a weapon to raise.", nil, {
data = {min = 0.1, max = 60, decimals = 1},
category = "server"
})
ix.config.Add("allowBusiness", true, "Whether or not business is enabled.", nil, {
category = "server"
})
ix.config.Add("maxHoldWeight", 100, "The maximum weight that a player can carry in their hands.", nil, {
data = {min = 1, max = 500},
category = "interaction"
})
ix.config.Add("throwForce", 732, "How hard a player can throw the item that they're holding.", nil, {
data = {min = 0, max = 8192},
category = "interaction"
})
ix.config.Add("allowPush", true, "Whether or not pushing with hands is allowed.", nil, {
category = "interaction"
})
ix.config.Add("itemPickupTime", 0.5, "How long it takes to pick up and put an item in your inventory.", nil, {
data = {min = 0, max = 5, decimals = 1},
category = "interaction"
})
ix.config.Add("year", 2015, "The current in-game year.", function(oldValue, newValue)
if (SERVER and !ix.date.bSaving) then
ix.date.ResolveOffset()
ix.date.current:setyear(newValue)
ix.date.Send()
end
end, {
data = {min = 1, max = 9999},
category = "date"
})
ix.config.Add("month", 1, "The current in-game month.", function(oldValue, newValue)
if (SERVER and !ix.date.bSaving) then
ix.date.ResolveOffset()
ix.date.current:setmonth(newValue)
ix.date.Send()
end
end, {
data = {min = 1, max = 12},
category = "date"
})
ix.config.Add("day", 1, "The current in-game day.", function(oldValue, newValue)
if (SERVER and !ix.date.bSaving) then
ix.date.ResolveOffset()
ix.date.current:setday(newValue)
ix.date.Send()
end
end, {
data = {min = 1, max = 31},
category = "date"
})
ix.config.Add("secondsPerMinute", 60, "How many seconds it takes for a minute to pass in-game.", function(oldValue, newValue)
if (SERVER and !ix.date.bSaving) then
ix.date.UpdateTimescale(newValue)
ix.date.Send()
end
end, {
data = {min = 0.01, max = 120},
category = "date"
})
================================================
FILE: gamemode/config/sh_options.lua
================================================
if (CLIENT) then
ix.option.Add("animationScale", ix.type.number, 1, {
category = "appearance", min = 0.3, max = 2, decimals = 1
})
ix.option.Add("24hourTime", ix.type.bool, false, {
category = "appearance"
})
ix.option.Add("altLower", ix.type.bool, true, {
category = "general"
})
ix.option.Add("alwaysShowBars", ix.type.bool, false, {
category = "appearance"
})
ix.option.Add("minimalTooltips", ix.type.bool, false, {
category = "appearance"
})
ix.option.Add("noticeDuration", ix.type.number, 8, {
category = "appearance", min = 0.1, max = 20, decimals = 1
})
ix.option.Add("noticeMax", ix.type.number, 4, {
category = "appearance", min = 1, max = 20
})
ix.option.Add("cheapBlur", ix.type.bool, false, {
category = "performance"
})
ix.option.Add("disableAnimations", ix.type.bool, false, {
category = "performance"
})
ix.option.Add("openBags", ix.type.bool, true, {
category = "general"
})
ix.option.Add("showIntro", ix.type.bool, true, {
category = "general"
})
ix.option.Add("escCloseMenu", ix.type.bool, false, {
category = "general"
})
end
ix.option.Add("language", ix.type.array, ix.config.language or "english", {
category = "general",
bNetworked = true,
populate = function()
local entries = {}
for k, _ in SortedPairs(ix.lang.stored) do
local name = ix.lang.names[k]
local name2 = k:utf8sub(1, 1):utf8upper() .. k:utf8sub(2)
if (name) then
name = name .. " (" .. name2 .. ")"
else
name = name2
end
entries[k] = name
end
return entries
end
})
================================================
FILE: gamemode/core/cl_skin.lua
================================================
local gradient = surface.GetTextureID("vgui/gradient-d")
local gradientUp = surface.GetTextureID("vgui/gradient-u")
local gradientLeft = surface.GetTextureID("vgui/gradient-l")
local gradientRadial = Material("helix/gui/radial-gradient.png")
local defaultBackgroundColor = Color(30, 30, 30, 200)
local SKIN = {}
derma.DefineSkin("helix", "The base skin for the Helix framework.", SKIN)
SKIN.fontCategory = "ixMediumLightFont"
SKIN.fontCategoryBlur = "ixMediumLightBlurFont"
SKIN.fontSegmentedProgress = "ixMediumLightFont"
SKIN.Colours = table.Copy(derma.SkinList.Default.Colours)
SKIN.Colours.Info = Color(100, 185, 255)
SKIN.Colours.Success = Color(64, 185, 85)
SKIN.Colours.Error = Color(255, 100, 100)
SKIN.Colours.Warning = Color(230, 180, 0)
SKIN.Colours.MenuLabel = color_white
SKIN.Colours.DarkerBackground = Color(0, 0, 0, 77)
SKIN.Colours.SegmentedProgress = {}
SKIN.Colours.SegmentedProgress.Bar = Color(64, 185, 85)
SKIN.Colours.SegmentedProgress.Text = color_white
SKIN.Colours.Area = {}
SKIN.Colours.Window.TitleActive = Color(0, 0, 0)
SKIN.Colours.Window.TitleInactive = color_white
SKIN.Colours.Button.Normal = color_white
SKIN.Colours.Button.Hover = color_white
SKIN.Colours.Button.Down = Color(180, 180, 180)
SKIN.Colours.Button.Disabled = Color(0, 0, 0, 100)
SKIN.Colours.Label.Default = color_white
function SKIN.tex.Menu_Strip(x, y, width, height, color)
surface.SetDrawColor(0, 0, 0, 200)
surface.DrawRect(x, y, width, height)
surface.SetDrawColor(ColorAlpha(color or ix.config.Get("color"), 175))
surface.SetTexture(gradient)
surface.DrawTexturedRect(x, y, width, height)
surface.SetTextColor(color_white)
end
hook.Add("ColorSchemeChanged", "ixSkin", function(color)
SKIN.Colours.Area.Background = color
end)
function SKIN:DrawHelixCurved(x, y, radius, segments, barHeight, fraction, color, altColor)
radius = radius or math.min(ScreenScale(72), 128) * 2
segments = segments or 76
barHeight = barHeight or 64
color = color or ix.config.Get("color")
altColor = altColor or Color(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
fraction = fraction or 1
surface.SetTexture(-1)
for i = 1, math.ceil(segments) do
local angle = math.rad((i / segments) * -360)
local barX = x + math.sin(angle + (fraction * math.pi * 2)) * radius
local barY = y + math.cos(angle + (fraction * math.pi * 2)) * radius
local barOffset = math.sin(SysTime() + i * 0.5)
if (barOffset > 0) then
surface.SetDrawColor(color)
else
surface.SetDrawColor(altColor)
end
surface.DrawTexturedRectRotated(barX, barY, 4, barOffset * (barHeight * fraction), math.deg(angle))
end
end
function SKIN:DrawHelix(x, y, width, height, segments, color, fraction, speed)
segments = segments or width * 0.05
color = color or ix.config.Get("color")
fraction = fraction or 0.25
speed = speed or 1
for i = 1, math.ceil(segments) do
local offset = math.sin((SysTime() + speed) + i * fraction)
local barHeight = height * offset
surface.SetTexture(-1)
if (offset > 0) then
surface.SetDrawColor(color)
else
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
end
surface.DrawTexturedRectRotated(x + (i / segments) * width, y + height * 0.5, 4, barHeight, 0)
end
end
function SKIN:PaintFrame(panel)
if (!panel.bNoBackgroundBlur) then
ix.util.DrawBlur(panel, 10)
end
surface.SetDrawColor(30, 30, 30, 150)
surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall())
if (panel:GetTitle() != "" or panel.btnClose:IsVisible()) then
surface.SetDrawColor(ix.config.Get("color"))
surface.DrawRect(0, 0, panel:GetWide(), 24)
if (panel.bHighlighted) then
self:DrawImportantBackground(0, 0, panel:GetWide(), 24, ColorAlpha(color_white, 22))
end
end
surface.SetDrawColor(ix.config.Get("color"))
surface.DrawOutlinedRect(0, 0, panel:GetWide(), panel:GetTall())
end
function SKIN:PaintBaseFrame(panel, width, height)
if (!panel.bNoBackgroundBlur) then
ix.util.DrawBlur(panel, 10)
end
surface.SetDrawColor(30, 30, 30, 150)
surface.DrawRect(0, 0, width, height)
surface.SetDrawColor(ix.config.Get("color"))
surface.DrawOutlinedRect(0, 0, width, height)
end
function SKIN:DrawImportantBackground(x, y, width, height, color)
color = color or defaultBackgroundColor
surface.SetTexture(gradientLeft)
surface.SetDrawColor(color)
surface.DrawTexturedRect(x, y, width, height)
end
function SKIN:DrawCharacterStatusBackground(panel, fraction)
surface.SetDrawColor(0, 0, 0, fraction * 100)
surface.DrawRect(0, 0, ScrW(), ScrH())
ix.util.DrawBlurAt(0, 0, ScrW(), ScrH(), 5, nil, fraction * 255)
end
function SKIN:PaintPanel(panel)
if (panel.m_bBackground) then
local width, height = panel:GetSize()
if (panel.m_bgColor) then
surface.SetDrawColor(panel.m_bgColor)
else
surface.SetDrawColor(30, 30, 30, 100)
end
surface.DrawRect(0, 0, width, height)
surface.SetDrawColor(0, 0, 0, 150)
surface.DrawOutlinedRect(0, 0, width, height)
end
end
function SKIN:PaintMenuBackground(panel, width, height, alphaFraction)
alphaFraction = alphaFraction or 1
surface.SetDrawColor(ColorAlpha(color_black, alphaFraction * 255))
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height)
ix.util.DrawBlur(panel, alphaFraction * 15, nil, 200)
end
function SKIN:PaintPlaceholderPanel(panel, width, height, barWidth, padding)
local size = math.max(width, height)
barWidth = barWidth or size * 0.05
local segments = size / barWidth
for i = 1, segments do
surface.SetTexture(-1)
surface.SetDrawColor(0, 0, 0, 88)
surface.DrawTexturedRectRotated(i * barWidth, i * barWidth, barWidth, size * 2, -45)
end
end
function SKIN:PaintCategoryPanel(panel, text, color)
text = text or ""
color = color or ix.config.Get("color")
surface.SetFont(self.fontCategoryBlur)
local textHeight = select(2, surface.GetTextSize(text)) + 6
local width, height = panel:GetSize()
surface.SetDrawColor(0, 0, 0, 100)
surface.DrawRect(0, textHeight, width, height - textHeight)
self:DrawImportantBackground(0, 0, width, textHeight, color)
surface.SetTextColor(color_black)
surface.SetTextPos(4, 4)
surface.DrawText(text)
surface.SetFont(self.fontCategory)
surface.SetTextColor(color_white)
surface.SetTextPos(4, 4)
surface.DrawText(text)
surface.SetDrawColor(color)
surface.DrawOutlinedRect(0, 0, width, height)
return 1, textHeight, 1, 1
end
function SKIN:PaintButton(panel)
if (panel.m_bBackground) then
local w, h = panel:GetWide(), panel:GetTall()
local alpha = 50
if (panel:GetDisabled()) then
alpha = 10
elseif (panel.Depressed) then
alpha = 180
elseif (panel.Hovered) then
alpha = 75
end
if (panel:GetParent() and panel:GetParent():GetName() == "DListView_Column") then
surface.SetDrawColor(color_white)
surface.DrawRect(0, 0, w, h)
end
surface.SetDrawColor(30, 30, 30, alpha)
surface.DrawRect(0, 0, w, h)
surface.SetDrawColor(0, 0, 0, 180)
surface.DrawOutlinedRect(0, 0, w, h)
surface.SetDrawColor(180, 180, 180, 2)
surface.DrawOutlinedRect(1, 1, w - 2, h - 2)
end
end
function SKIN:PaintEntityInfoBackground(panel, width, height)
ix.util.DrawBlur(panel, 1)
surface.SetDrawColor(self.Colours.DarkerBackground)
surface.DrawRect(0, 0, width, height)
end
function SKIN:PaintTooltipBackground(panel, width, height)
ix.util.DrawBlur(panel, 1)
surface.SetDrawColor(self.Colours.DarkerBackground)
surface.DrawRect(0, 0, width, height)
end
function SKIN:PaintTooltipMinimalBackground(panel, width, height)
surface.SetDrawColor(0, 0, 0, 150 * panel.fraction)
surface.SetMaterial(gradientRadial)
surface.DrawTexturedRect(0, 0, width, height)
end
function SKIN:PaintSegmentedProgressBackground(panel, width, height)
end
function SKIN:PaintSegmentedProgress(panel, width, height)
local font = panel:GetFont() or self.fontSegmentedProgress
local textColor = panel:GetTextColor() or self.Colours.SegmentedProgress.Text
local barColor = panel:GetBarColor() or self.Colours.SegmentedProgress.Bar
local segments = panel:GetSegments()
local segmentHalfWidth = width / #segments * 0.5
surface.SetDrawColor(barColor)
surface.DrawRect(0, 0, panel:GetFraction() * width, height)
for i = 1, #segments do
local text = segments[i]
local x = (i - 1) / #segments * width + segmentHalfWidth
local y = height * 0.5
draw.SimpleText(text, font, x, y, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
function SKIN:PaintCharacterCreateBackground(panel, width, height)
surface.SetDrawColor(40, 40, 40, 255)
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height)
end
function SKIN:PaintCharacterLoadBackground(panel, width, height)
surface.SetDrawColor(40, 40, 40, panel:GetBackgroundFraction() * 255)
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height)
end
function SKIN:PaintCharacterTransitionOverlay(panel, x, y, width, height, color)
color = color or ix.config.Get("color")
surface.SetDrawColor(color)
surface.DrawRect(x, y, width, height)
end
function SKIN:PaintAreaEntry(panel, width, height)
local color = ColorAlpha(panel:GetBackgroundColor() or self.Colours.Area.Background, panel:GetBackgroundAlpha())
self:DrawImportantBackground(0, 0, width, height, color)
end
function SKIN:PaintListRow(panel, width, height)
surface.SetDrawColor(0, 0, 0, 150)
surface.DrawRect(0, 0, width, height)
end
function SKIN:PaintSettingsRowBackground(panel, width, height)
local index = panel:GetBackgroundIndex()
local bReset = panel:GetShowReset()
if (index == 0) then
surface.SetDrawColor(30, 30, 30, 45)
surface.DrawRect(0, 0, width, height)
end
if (bReset) then
surface.SetDrawColor(self.Colours.Warning)
surface.DrawRect(0, 0, 2, height)
end
end
function SKIN:PaintVScrollBar(panel, width, height)
end
function SKIN:PaintHScrollBar(panel, width, height)
end
function SKIN:PaintScrollBarGrip(panel, width, height)
local parent = panel:GetParent()
if (IsValid(parent.btnUp)) then
-- Vertical scrollbar
local upButtonHeight = parent.btnUp:GetTall()
local downButtonHeight = parent.btnDown:GetTall()
DisableClipping(true)
surface.SetDrawColor(30, 30, 30, 200)
surface.DrawRect(4, -upButtonHeight, width - 8, height + upButtonHeight + downButtonHeight)
DisableClipping(false)
else
-- Horizontal scrollbar
local leftButtonWidth = parent.btnLeft:GetWide()
local rightButtonWidth = parent.btnRight:GetWide()
DisableClipping(true)
surface.SetDrawColor(30, 30, 30, 200)
surface.DrawRect(-leftButtonWidth, 4, width + leftButtonWidth + rightButtonWidth, height - 8)
DisableClipping(false)
end
end
function SKIN:PaintButtonUp(panel, width, height)
end
function SKIN:PaintButtonDown(panel, width, height)
end
function SKIN:PaintButtonLeft(panel, width, height)
end
function SKIN:PaintButtonRight(panel, width, height)
end
function SKIN:PaintComboBox(panel, width, height)
end
function SKIN:PaintComboDownArrow(panel, width, height)
surface.SetFont("ixIconsSmall")
local textWidth, textHeight = surface.GetTextSize("r")
local alpha = (panel.ComboBox:IsMenuOpen() or panel.ComboBox.Hovered) and 200 or 100
surface.SetTextColor(ColorAlpha(ix.config.Get("color"), alpha))
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5)
surface.DrawText("r")
end
function SKIN:PaintMenu(panel, width, height)
ix.util.DrawBlur(panel)
surface.SetDrawColor(30, 30, 30, 150)
surface.DrawRect(0, 0, width, height)
end
function SKIN:PaintMenuOption(panel, width, height)
if (panel.m_bBackground and (panel.Hovered or panel.Highlight)) then
self:DrawImportantBackground(0, 0, width, height, ix.config.Get("color"))
end
end
function SKIN:PaintHelixSlider(panel, width, height)
surface.SetDrawColor(self.Colours.DarkerBackground)
surface.DrawRect(0, 0, width, height)
surface.SetDrawColor(self.Colours.Success)
surface.DrawRect(0, 0, panel:GetVisualFraction() * width, height)
end
function SKIN:PaintChatboxTabButton(panel, width, height)
if (panel:GetActive()) then
surface.SetDrawColor(ix.config.Get("color", Color(75, 119, 190, 255)))
surface.DrawRect(0, 0, width, height)
else
surface.SetDrawColor(0, 0, 0, 100)
surface.DrawRect(0, 0, width, height)
if (panel:GetUnread()) then
surface.SetDrawColor(ColorAlpha(self.Colours.Warning, Lerp(panel.unreadAlpha, 0, 100)))
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height - 1)
end
end
-- border
surface.SetDrawColor(color_black)
surface.DrawRect(width - 1, 0, 1, height) -- right
end
function SKIN:PaintChatboxTabs(panel, width, height, alpha)
surface.SetDrawColor(0, 0, 0, 33)
surface.DrawRect(0, 0, width, height)
surface.SetDrawColor(0, 0, 0, 100)
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, height * 0.5, width, height * 0.5)
local tab = panel:GetActiveTab()
if (tab) then
local button = tab:GetButton()
local x, _ = button:GetPos()
-- outline
surface.SetDrawColor(0, 0, 0, 200)
surface.DrawRect(0, height - 1, x, 1) -- left
surface.DrawRect(x + button:GetWide(), height - 1, width - x - button:GetWide(), 1) -- right
end
end
function SKIN:PaintChatboxBackground(panel, width, height)
ix.util.DrawBlur(panel, 10)
if (panel:GetActive()) then
surface.SetDrawColor(ColorAlpha(ix.config.Get("color"), 120))
surface.SetTexture(gradientUp)
surface.DrawTexturedRect(0, panel.tabs.buttons:GetTall(), width, height * 0.25)
end
surface.SetDrawColor(color_black)
surface.DrawOutlinedRect(0, 0, width, height)
end
function SKIN:PaintChatboxEntry(panel, width, height)
surface.SetDrawColor(0, 0, 0, 66)
surface.DrawRect(0, 0, width, height)
panel:DrawTextEntryText(color_white, ix.config.Get("color"), color_white)
surface.SetDrawColor(color_black)
surface.DrawOutlinedRect(0, 0, width, height)
end
function SKIN:DrawChatboxPreviewBox(x, y, text, color)
color = color or ix.config.Get("color")
local textWidth, textHeight = surface.GetTextSize(text)
local width, height = textWidth + 8, textHeight + 8
-- background
surface.SetDrawColor(color)
surface.DrawRect(x, y, width, height)
-- text
surface.SetTextColor(color_white)
surface.SetTextPos(x + width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
surface.DrawText(text)
-- outline
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255)
surface.DrawOutlinedRect(x, y, width, height)
return width
end
function SKIN:DrawChatboxPrefixBox(panel, width, height)
local color = panel:GetBackgroundColor()
-- background
surface.SetDrawColor(color)
surface.DrawRect(0, 0, width, height)
-- outline
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255)
surface.DrawOutlinedRect(0, 0, width, height)
end
function SKIN:PaintChatboxAutocompleteEntry(panel, width, height)
-- selected background
if (panel.highlightAlpha > 0) then
self:DrawImportantBackground(0, 0, width, height, ColorAlpha(ix.config.Get("color"), panel.highlightAlpha * 66))
end
-- lower border
surface.SetDrawColor(200, 200, 200, 33)
surface.DrawRect(0, height - 1, width, 1)
end
function SKIN:PaintWindowMinimizeButton(panel, width, height)
end
function SKIN:PaintWindowMaximizeButton(panel, width, height)
end
function SKIN:PaintInfoBar(panel, width, height, color)
-- bar
surface.SetDrawColor(color.r, color.g, color.b, 250)
surface.DrawRect(0, 0, width, height)
-- gradient overlay
surface.SetDrawColor(230, 230, 230, 8)
surface.SetTexture(gradientUp)
surface.DrawTexturedRect(0, 0, width, height)
end
function SKIN:PaintInfoBarBackground(panel, width, height)
surface.SetDrawColor(230, 230, 230, 15)
surface.DrawRect(0, 0, width, height)
surface.DrawOutlinedRect(0, 0, width, height)
panel.bar:PaintManual()
DisableClipping(true)
panel.label:PaintManual()
DisableClipping(false)
end
function SKIN:PaintInventorySlot(panel, width, height)
surface.SetDrawColor(35, 35, 35, 85)
surface.DrawRect(1, 1, width - 2, height - 2)
surface.SetDrawColor(0, 0, 0, 250)
surface.DrawOutlinedRect(1, 1, width - 2, height - 2)
end
function SKIN:PaintDeathScreenBackground(panel, width, height, progress)
surface.SetDrawColor(0, 0, 0, (progress / 0.3) * 255)
surface.DrawRect(0, 0, width, height)
end
function SKIN:PaintDeathScreen(panel, width, height, progress)
ix.bar.DrawAction()
end
do
-- check if sounds exist, otherwise fall back to default UI sounds
local bWhoosh = file.Exists("sound/helix/ui/whoosh1.wav", "GAME")
local bRollover = file.Exists("sound/helix/ui/rollover.wav", "GAME")
local bPress = file.Exists("sound/helix/ui/press.wav", "GAME")
local bNotify = file.Exists("sound/helix/ui/REPLACEME.wav", "GAME") -- @todo
sound.Add({
name = "Helix.Whoosh",
channel = CHAN_STATIC,
volume = 0.4,
level = 80,
pitch = bWhoosh and {90, 105} or 100,
sound = bWhoosh and {
"helix/ui/whoosh1.wav",
"helix/ui/whoosh2.wav",
"helix/ui/whoosh3.wav",
"helix/ui/whoosh4.wav",
"helix/ui/whoosh5.wav",
"helix/ui/whoosh6.wav"
} or ""
})
sound.Add({
name = "Helix.Rollover",
channel = CHAN_STATIC,
volume = 0.5,
level = 80,
pitch = {95, 105},
sound = bRollover and "helix/ui/rollover.wav" or "ui/buttonrollover.wav"
})
sound.Add({
name = "Helix.Press",
channel = CHAN_STATIC,
volume = 0.5,
level = 80,
pitch = bPress and {95, 110} or 100,
sound = bPress and "helix/ui/press.wav" or "ui/buttonclickrelease.wav"
})
sound.Add({
name = "Helix.Notify",
channel = CHAN_STATIC,
volume = 0.35,
level = 80,
pitch = 140,
sound = bNotify and "helix/ui/REPLACEME.wav" or "weapons/grenade/tick1.wav"
})
end
derma.RefreshSkins()
================================================
FILE: gamemode/core/derma/cl_attribute.lua
================================================
local gradient = ix.util.GetMaterial("vgui/gradient-u")
local gradient2 = ix.util.GetMaterial("vgui/gradient-d")
local PANEL = {}
AccessorFunc(PANEL, "color", "Color")
AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER)
AccessorFunc(PANEL, "boostValue", "Boost", FORCE_NUMBER)
AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER)
function PANEL:Init()
self:SetTall(20)
self.add = self:Add("DImageButton")
self.add:SetSize(16, 16)
self.add:Dock(RIGHT)
self.add:DockMargin(2, 2, 2, 2)
self.add:SetImage("icon16/add.png")
self.add.OnMousePressed = function()
self.pressing = 1
self:DoChange()
self.add:SetAlpha(150)
end
self.add.OnMouseReleased = function()
if (self.pressing) then
self.pressing = nil
self.add:SetAlpha(255)
end
end
self.add.OnCursorExited = self.add.OnMouseReleased
self.sub = self:Add("DImageButton")
self.sub:SetSize(16, 16)
self.sub:Dock(LEFT)
self.sub:DockMargin(2, 2, 2, 2)
self.sub:SetImage("icon16/delete.png")
self.sub.OnMousePressed = function()
self.pressing = -1
self:DoChange()
self.sub:SetAlpha(150)
end
self.sub.OnMouseReleased = function()
if (self.pressing) then
self.pressing = nil
self.sub:SetAlpha(255)
end
end
self.sub.OnCursorExited = self.sub.OnMouseReleased
self.value = 0
self.deltaValue = self.value
self.max = 10
self.animateSpeed = 15
self.bar = self:Add("DPanel")
self.bar:Dock(FILL)
self.bar.Paint = function(this, w, h)
surface.SetDrawColor(35, 35, 35, 250)
surface.DrawRect(0, 0, w, h)
w, h = w - 4, h - 4
local value = self.deltaValue / self.max
if (value > 0) then
local color = self.color and self.color or ix.config.Get("color")
local boostedValue = self.boostValue or 0
local add = 0
if (self.deltaValue != self.value) then
add = 35
end
-- your stat
do
if !(boostedValue < 0 and math.abs(boostedValue) > self.value) then
surface.SetDrawColor(color.r + add, color.g + add, color.b + add, 230)
surface.DrawRect(2, 2, w * value, h)
surface.SetDrawColor(255, 255, 255, 35)
surface.SetMaterial(gradient)
surface.DrawTexturedRect(2, 2, w * value, h)
end
end
-- boosted stat
do
local boostValue
if (boostedValue != 0) then
if (boostedValue < 0) then
local please = math.min(self.value, math.abs(boostedValue))
boostValue = ((please or 0) / self.max) * (self.deltaValue / self.value)
else
boostValue = ((boostedValue or 0) / self.max) * (self.deltaValue / self.value)
end
if (boostedValue < 0) then
surface.SetDrawColor(200, 40, 40, 230)
local bWidth = math.abs(w * boostValue)
surface.DrawRect(2 + w * value - bWidth, 2, bWidth, h)
surface.SetDrawColor(255, 255, 255, 35)
surface.SetMaterial(gradient)
surface.DrawTexturedRect(2 + w * value - bWidth, 2, bWidth, h)
else
surface.SetDrawColor(40, 200, 40, 230)
surface.DrawRect(2 + w * value, 2, w * boostValue, h)
surface.SetDrawColor(255, 255, 255, 35)
surface.SetMaterial(gradient)
surface.DrawTexturedRect(2 + w * value, 2, w * boostValue, h)
end
end
end
end
surface.SetDrawColor(255, 255, 255, 5)
surface.SetMaterial(gradient2)
surface.DrawTexturedRect(2, 2, w, h)
end
self.label = self.bar:Add("DLabel")
self.label:Dock(FILL)
self.label:SetExpensiveShadow(1, Color(0, 0, 60))
self.label:SetContentAlignment(5)
end
function PANEL:Think()
if (self.pressing) then
if ((self.nextPress or 0) < CurTime()) then
self:DoChange()
end
end
self.deltaValue = math.Approach(self.deltaValue, self.value, FrameTime() * self.animateSpeed)
end
function PANEL:DoChange()
if ((self.value == 0 and self.pressing == -1) or (self.value == self.max and self.pressing == 1)) then
return
end
self.nextPress = CurTime() + 0.2
if (self:OnChanged(self.pressing) != false) then
self.value = math.Clamp(self.value + self.pressing, 0, self.max)
end
end
function PANEL:OnChanged(difference)
end
function PANEL:SetText(text)
self.label:SetText(text)
end
function PANEL:SetReadOnly()
self.sub:Remove()
self.add:Remove()
end
vgui.Register("ixAttributeBar", PANEL, "DPanel")
================================================
FILE: gamemode/core/derma/cl_bar.lua
================================================
-- bar manager
-- this manages positions for bar panels
local PANEL = {}
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
function PANEL:Init()
self:SetSize(ScrW() * 0.35, ScrH())
self:SetPos(4, 4)
self:ParentToHUD()
self.bars = {}
self.padding = 2
-- add bars that were registered before manager creation
for _, v in ipairs(ix.bar.list) do
v.panel = self:AddBar(v.index, v.color, v.priority)
end
end
function PANEL:GetAll()
return self.bars
end
function PANEL:Clear()
for k, v in ipairs(self.bars) do
v:Remove()
table.remove(self.bars, k)
end
end
function PANEL:AddBar(index, color, priority)
local panel = self:Add("ixInfoBar")
panel:SetSize(self:GetWide(), BAR_HEIGHT)
panel:SetVisible(false)
panel:SetID(index)
panel:SetColor(color)
panel:SetPriority(priority)
self.bars[#self.bars + 1] = panel
self:Sort()
return panel
end
function PANEL:RemoveBar(panel)
local toRemove
for k, v in ipairs(self.bars) do
if (v == panel) then
toRemove = k
break
end
end
if (toRemove) then
table.remove(self.bars, toRemove)
-- Decrease index value for the next bars
for i = toRemove, #self.bars do
ix.bar.list[i].index = i
self.bars[i]:SetID(i)
end
end
panel:Remove()
self:Sort()
end
-- sort bars by priority
function PANEL:Sort()
table.sort(self.bars, function(a, b)
return a:GetPriority() < b:GetPriority()
end)
end
-- update target Y positions
function PANEL:Organize()
local currentY = 0
for _, v in ipairs(self.bars) do
if (!v:IsVisible()) then
continue
end
v:SetPos(0, currentY)
currentY = currentY + self.padding + v:GetTall()
end
self:SetSize(self:GetWide(), currentY)
end
function PANEL:Think()
local menu = (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) and ix.gui.characterMenu
or IsValid(ix.gui.menu) and ix.gui.menu
local fraction = menu and 1 - menu.currentAlpha / 255 or 1
self:SetAlpha(255 * fraction)
-- don't update bars when not visible
if (fraction == 0) then
return
end
local curTime = CurTime()
local bShouldHide = hook.Run("ShouldHideBars")
local bAlwaysShow = ix.option.Get("alwaysShowBars", false)
for _, v in ipairs(self.bars) do
local info = ix.bar.list[v:GetID()]
local realValue, barText = info.GetValue()
if (bShouldHide or realValue == false) then
v:SetVisible(false)
continue
end
if (v:GetDelta() != realValue) then
v:SetLifetime(curTime + 5)
end
if (v:GetLifetime() < curTime and !info.visible and !bAlwaysShow and !hook.Run("ShouldBarDraw", info)) then
v:SetVisible(false)
continue
end
v:SetVisible(true)
v:SetValue(realValue)
v:SetText(isstring(barText) and barText or "")
end
self:Organize()
end
function PANEL:OnRemove()
self:Clear()
end
vgui.Register("ixInfoBarManager", PANEL, "Panel")
PANEL = {}
AccessorFunc(PANEL, "index", "ID", FORCE_NUMBER)
AccessorFunc(PANEL, "color", "Color")
AccessorFunc(PANEL, "priority", "Priority", FORCE_NUMBER)
AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER)
AccessorFunc(PANEL, "delta", "Delta", FORCE_NUMBER)
AccessorFunc(PANEL, "lifetime", "Lifetime", FORCE_NUMBER)
function PANEL:Init()
self.value = 0
self.delta = 0
self.lifetime = 0
self.bar = self:Add("DPanel")
self.bar:SetPaintedManually(true)
self.bar:Dock(FILL)
self.bar:DockMargin(2, 2, 2, 2)
self.bar.Paint = function(this, width, height)
width = width * math.min(self.delta, 1)
derma.SkinFunc("PaintInfoBar", self, width, height, self.color)
end
self.label = self:Add("DLabel")
self.label:SetFont("ixSmallFont")
self.label:SetContentAlignment(5)
self.label:SetText("")
self.label:SetTextColor(Color(240, 240, 240))
self.label:SetExpensiveShadow(2, Color(20, 20, 20))
self.label:SetPaintedManually(true)
self.label:SizeToContents()
self.label:Dock(FILL)
end
function PANEL:SetText(text)
self.label:SetText(text)
self.label:SizeToContents()
end
function PANEL:Think()
self.delta = math.Approach(self.delta, self.value, FrameTime())
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintInfoBarBackground", self, width, height)
end
vgui.Register("ixInfoBar", PANEL, "Panel")
if (IsValid(ix.gui.bars)) then
ix.gui.bars:Remove()
ix.gui.bars = vgui.Create("ixInfoBarManager")
end
================================================
FILE: gamemode/core/derma/cl_business.lua
================================================
local PANEL = {}
function PANEL:Init()
-- being relative.
local size = 120
self:SetSize(size, size * 1.4)
end
function PANEL:SetItem(itemTable)
self.itemName = L(itemTable.name):lower()
self.price = self:Add("DLabel")
self.price:Dock(BOTTOM)
self.price:SetText(itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper())
self.price:SetContentAlignment(5)
self.price:SetTextColor(color_white)
self.price:SetFont("ixSmallFont")
self.price:SetExpensiveShadow(1, Color(0, 0, 0, 200))
self.name = self:Add("DLabel")
self.name:Dock(TOP)
self.name:SetText(itemTable.GetName and itemTable:GetName() or L(itemTable.name))
self.name:SetContentAlignment(5)
self.name:SetTextColor(color_white)
self.name:SetFont("ixSmallFont")
self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200))
self.name.Paint = function(this, w, h)
surface.SetDrawColor(0, 0, 0, 75)
surface.DrawRect(0, 0, w, h)
end
self.icon = self:Add("SpawnIcon")
self.icon:SetZPos(1)
self.icon:SetSize(self:GetWide(), self:GetWide())
self.icon:Dock(FILL)
self.icon:DockMargin(5, 5, 5, 10)
self.icon:InvalidateLayout(true)
self.icon:SetModel(itemTable:GetModel(), itemTable:GetSkin())
self.icon:SetHelixTooltip(function(tooltip)
ix.hud.PopulateItemTooltip(tooltip, itemTable)
end)
self.icon.itemTable = itemTable
self.icon.DoClick = function(this)
if (IsValid(ix.gui.checkout)) then
return
end
local parent = ix.gui.business
local bAdded = parent:BuyItem(itemTable.uniqueID)
if (bAdded) then
surface.PlaySound("buttons/button14.wav")
end
end
self.icon.PaintOver = function(this)
if (itemTable and itemTable.PaintOver) then
local w, h = this:GetSize()
itemTable.PaintOver(this, itemTable, w, h)
end
end
if ((itemTable.iconCam and !ICON_RENDER_QUEUE[itemTable.uniqueID]) or itemTable.forceRender) then
local iconCam = itemTable.iconCam
iconCam = {
cam_pos = iconCam.pos,
cam_fov = iconCam.fov,
cam_ang = iconCam.ang,
}
ICON_RENDER_QUEUE[itemTable.uniqueID] = true
self.icon:RebuildSpawnIconEx(
iconCam
)
end
end
vgui.Register("ixBusinessItem", PANEL, "DPanel")
PANEL = {}
function PANEL:Init()
ix.gui.business = self
self:SetSize(self:GetParent():GetSize())
self.categories = self:Add("DScrollPanel")
self.categories:Dock(LEFT)
self.categories:SetWide(260)
self.categories.Paint = function(this, w, h)
surface.SetDrawColor(0, 0, 0, 150)
surface.DrawRect(0, 0, w, h)
end
self.categories:DockPadding(5, 5, 5, 5)
self.categories:DockMargin(0, 46, 0, 0)
self.categoryPanels = {}
self.scroll = self:Add("DScrollPanel")
self.scroll:Dock(FILL)
self.search = self:Add("DTextEntry")
self.search:Dock(TOP)
self.search:SetTall(36)
self.search:SetFont("ixMediumFont")
self.search:DockMargin(10 + self:GetWide() * 0.35, 0, 5, 5)
self.search.OnTextChanged = function(this)
local text = self.search:GetText():lower()
if (self.selected) then
self:LoadItems(self.selected.category, text:find("%S") and text or nil)
self.scroll:InvalidateLayout()
end
end
self.search.PaintOver = function(this, cw, ch)
if (self.search:GetValue() == "" and !self.search:HasFocus()) then
ix.util.DrawText("V", 10, ch/2 - 1, color_black, 3, 1, "ixIconsSmall")
end
end
self.itemList = self.scroll:Add("DIconLayout")
self.itemList:Dock(TOP)
self.itemList:DockMargin(10, 1, 5, 5)
self.itemList:SetSpaceX(10)
self.itemList:SetSpaceY(10)
self.itemList:SetMinimumSize(128, 400)
self.checkout = self:Add("DButton")
self.checkout:Dock(BOTTOM)
self.checkout:SetTextColor(color_white)
self.checkout:SetTall(36)
self.checkout:SetFont("ixMediumFont")
self.checkout:DockMargin(10, 10, 0, 0)
self.checkout:SetExpensiveShadow(1, Color(0, 0, 0, 150))
self.checkout:SetText(L("checkout", 0))
self.checkout.DoClick = function()
if (!IsValid(ix.gui.checkout) and self:GetCartCount() > 0) then
vgui.Create("ixBusinessCheckout"):SetCart(self.cart)
end
end
self.cart = {}
local dark = Color(0, 0, 0, 50)
local first = true
for k, v in pairs(ix.item.list) do
if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), k) == false) then
continue
end
if (!self.categoryPanels[L(v.category)]) then
self.categoryPanels[L(v.category)] = v.category
end
end
for category, realName in SortedPairs(self.categoryPanels) do
local button = self.categories:Add("DButton")
button:SetTall(36)
button:SetText(category)
button:Dock(TOP)
button:SetTextColor(color_white)
button:DockMargin(5, 5, 5, 0)
button:SetFont("ixMediumFont")
button:SetExpensiveShadow(1, Color(0, 0, 0, 150))
button.Paint = function(this, w, h)
surface.SetDrawColor(self.selected == this and ix.config.Get("color") or dark)
surface.DrawRect(0, 0, w, h)
surface.SetDrawColor(0, 0, 0, 50)
surface.DrawOutlinedRect(0, 0, w, h)
end
button.DoClick = function(this)
if (self.selected != this) then
self.selected = this
self:LoadItems(realName)
timer.Simple(0.01, function()
self.scroll:InvalidateLayout()
end)
end
end
button.category = realName
if (first) then
self.selected = button
first = false
end
self.categoryPanels[realName] = button
end
if (self.selected) then
self:LoadItems(self.selected.category)
end
end
function PANEL:GetCartCount()
local count = 0
for _, v in pairs(self.cart) do
count = count + v
end
return count
end
function PANEL:BuyItem(uniqueID)
local currentCount = self.cart[uniqueID] or 0
if (currentCount >= 10) then
return false
end
self.cart[uniqueID] = currentCount + 1
self.checkout:SetText(L("checkout", self:GetCartCount()))
return true
end
function PANEL:LoadItems(category, search)
category = category or "misc"
local items = ix.item.list
self.itemList:Clear()
self.itemList:InvalidateLayout(true)
for uniqueID, itemTable in SortedPairsByMemberValue(items, "name") do
if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), uniqueID) == false) then
continue
end
if (itemTable.category == category) then
if (search and search != "" and !L(itemTable.name):lower():find(search, 1, true)) then
continue
end
self.itemList:Add("ixBusinessItem"):SetItem(itemTable)
end
end
end
function PANEL:SetPage()
end
function PANEL:GetPageItems()
end
vgui.Register("ixBusiness", PANEL, "EditablePanel")
DEFINE_BASECLASS("DFrame")
PANEL = {}
function PANEL:Init()
if (IsValid(ix.gui.checkout)) then
ix.gui.checkout:Remove()
end
ix.gui.checkout = self
self:SetTitle(L("checkout", 0))
self:SetSize(ScrW() / 4 > 200 and ScrW() / 4 or ScrW() / 2, ScrH() / 2 > 300 and ScrH() / 2 or ScrH())
self:MakePopup()
self:Center()
self:SetBackgroundBlur(true)
self:SetSizable(true)
self.items = self:Add("DScrollPanel")
self.items:Dock(FILL)
self.items.Paint = function(this, w, h)
surface.SetDrawColor(0, 0, 0, 100)
surface.DrawRect(0, 0, w, h)
end
self.items:DockMargin(0, 0, 0, 4)
self.buy = self:Add("DButton")
self.buy:Dock(BOTTOM)
self.buy:SetText(L"purchase")
self.buy:SetTextColor(color_white)
self.buy.DoClick = function(this)
if ((this.nextClick or 0) < CurTime()) then
this.nextClick = CurTime() + 0.5
else
return
end
if (self.preventBuy) then
self.finalGlow:SetText(self.final:GetText())
self.finalGlow:SetAlpha(255)
self.finalGlow:AlphaTo(0, 0.5)
return surface.PlaySound("buttons/button11.wav")
end
net.Start("ixBusinessBuy")
net.WriteUInt(table.Count(self.itemData), 8)
for k, v in pairs(self.itemData) do
net.WriteString(k)
net.WriteUInt(v, 8)
end
net.SendToServer()
self.itemData = {}
self.items:Remove()
self.data:Remove()
self.buy:Remove()
self:ShowCloseButton(false)
if (IsValid(ix.gui.business)) then
ix.gui.business.cart = {}
ix.gui.business.checkout:SetText(L("checkout", 0))
end
self.text = self:Add("DLabel")
self.text:Dock(FILL)
self.text:SetContentAlignment(5)
self.text:SetTextColor(color_white)
self.text:SetText(L"purchasing")
self.text:SetFont("ixMediumFont")
net.Receive("ixBusinessResponse", function()
if (IsValid(self)) then
self.text:SetText(L"buyGood")
self.done = true
self:ShowCloseButton(true)
surface.PlaySound("buttons/button3.wav")
end
end)
timer.Simple(4, function()
if (IsValid(self) and !self.done) then
self.text:SetText(L"buyFailed")
self:ShowCloseButton(true)
surface.PlaySound("buttons/button11.wav")
end
end)
end
self.data = self:Add("DPanel")
self.data:Dock(BOTTOM)
self.data:SetTall(64)
self.data:DockMargin(0, 0, 0, 4)
self.current = self.data:Add("DLabel")
self.current:SetFont("ixSmallFont")
self.current:SetContentAlignment(6)
self.current:SetTextColor(color_white)
self.current:Dock(TOP)
self.current:SetTextInset(4, 0)
self.total = self.data:Add("DLabel")
self.total:SetFont("ixSmallFont")
self.total:SetContentAlignment(6)
self.total:SetTextColor(color_white)
self.total:Dock(TOP)
self.total:SetTextInset(4, 0)
local line = self.data:Add("DPanel")
line:SetTall(1)
line:DockMargin(128, 0, 4, 0)
line:Dock(TOP)
line.Paint = function(this, w, h)
surface.SetDrawColor(255, 255, 255, 150)
surface.DrawLine(0, 0, w, 0)
end
self.final = self.data:Add("DLabel")
self.final:SetFont("ixSmallFont")
self.final:SetContentAlignment(6)
self.final:SetTextColor(color_white)
self.final:Dock(TOP)
self.final:SetTextInset(4, 0)
self.finalGlow = self.final:Add("DLabel")
self.finalGlow:Dock(FILL)
self.finalGlow:SetFont("ixSmallFont")
self.finalGlow:SetTextColor(color_white)
self.finalGlow:SetContentAlignment(6)
self.finalGlow:SetAlpha(0)
self.finalGlow:SetTextInset(4, 0)
self:SetFocusTopLevel(true)
self.itemData = {}
self:OnQuantityChanged()
end
function PANEL:OnQuantityChanged()
local price = 0
local money = LocalPlayer():GetCharacter():GetMoney()
local valid = 0
for k, v in pairs(self.itemData) do
local itemTable = ix.item.list[k]
if (itemTable and v > 0) then
valid = valid + 1
price = price + (v * (itemTable.price or 0))
end
end
self.current:SetText(L"currentMoney" .. ix.currency.Get(money))
self.total:SetText("- " .. ix.currency.Get(price))
self.final:SetText(L"moneyLeft" .. ix.currency.Get(money - price))
self.final:SetTextColor((money - price) >= 0 and Color(46, 204, 113) or Color(217, 30, 24))
self.preventBuy = (money - price) < 0 or valid == 0
if (IsValid(ix.gui.business)) then
ix.gui.business.checkout:SetText(L("checkout", ix.gui.business:GetCartCount()))
end
end
function PANEL:SetCart(items)
self.itemData = items
for k, v in SortedPairs(items) do
local itemTable = ix.item.list[k]
if (itemTable) then
local slot = self.items:Add("DPanel")
slot:SetTall(36)
slot:Dock(TOP)
slot:DockMargin(5, 5, 5, 0)
slot.icon = slot:Add("SpawnIcon")
slot.icon:SetPos(2, 2)
slot.icon:SetSize(32, 32)
slot.icon:SetModel(itemTable:GetModel())
slot.icon:SetTooltip()
slot.name = slot:Add("DLabel")
slot.name:SetPos(40, 2)
slot.name:SetFont("ixChatFont")
slot.name:SetText(string.format(
"%s (%s)",
L(itemTable.GetName and itemTable:GetName() or L(itemTable.name)),
itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper()
))
slot.name:SetTextColor(color_white)
slot.name:SizeToContents()
slot.name:DockMargin(40, 0, 0, 0)
slot.name:Dock(FILL)
slot.quantity = slot:Add("DTextEntry")
slot.quantity:SetSize(32, 32)
slot.quantity:Dock(RIGHT)
slot.quantity:DockMargin(4, 4, 4, 4)
slot.quantity:SetContentAlignment(5)
slot.quantity:SetNumeric(true)
slot.quantity:SetText(v)
slot.quantity:SetFont("ixChatFont")
slot.quantity.OnTextChanged = function(this)
local value = tonumber(this:GetValue())
if (!value) then
this:SetValue(1)
return
end
value = math.Clamp(math.Round(value), 0, 10)
if (value == 0) then
items[k] = nil
slot:Remove()
else
items[k] = value
end
self:OnQuantityChanged()
end
slot.quantity.OnLoseFocus = function(this)
local value = math.Clamp(tonumber(this:GetValue()) or 1, 0, 10)
this:SetText(value)
end
else
items[k] = nil
end
end
self:OnQuantityChanged()
end
function PANEL:Think()
if (!self:HasFocus()) then
self:MakePopup()
end
BaseClass.Think(self)
end
vgui.Register("ixBusinessCheckout", PANEL, "DFrame")
hook.Add("CreateMenuButtons", "ixBusiness", function(tabs)
if (hook.Run("BuildBusinessMenu") != false) then
tabs["business"] = function(container)
container:Add("ixBusiness")
end
end
end)
================================================
FILE: gamemode/core/derma/cl_character.lua
================================================
local gradient = surface.GetTextureID("vgui/gradient-d")
local audioFadeInTime = 2
local animationTime = 0.5
local matrixZScale = Vector(1, 1, 0.0001)
-- character menu panel
DEFINE_BASECLASS("ixSubpanelParent")
local PANEL = {}
function PANEL:Init()
self:SetSize(self:GetParent():GetSize())
self:SetPos(0, 0)
self.childPanels = {}
self.subpanels = {}
self.activeSubpanel = ""
self.currentDimAmount = 0
self.currentY = 0
self.currentScale = 1
self.currentAlpha = 255
self.targetDimAmount = 255
self.targetScale = 0.9
end
function PANEL:Dim(length, callback)
length = length or animationTime
self.currentDimAmount = 0
self:CreateAnimation(length, {
target = {
currentDimAmount = self.targetDimAmount,
currentScale = self.targetScale
},
easing = "outCubic",
OnComplete = callback
})
self:OnDim()
end
function PANEL:Undim(length, callback)
length = length or animationTime
self.currentDimAmount = self.targetDimAmount
self:CreateAnimation(length, {
target = {
currentDimAmount = 0,
currentScale = 1
},
easing = "outCubic",
OnComplete = callback
})
self:OnUndim()
end
function PANEL:OnDim()
end
function PANEL:OnUndim()
end
function PANEL:Paint(width, height)
local amount = self.currentDimAmount
local bShouldScale = self.currentScale != 1
local matrix
-- draw child panels with scaling if needed
if (bShouldScale) then
matrix = Matrix()
matrix:Scale(matrixZScale * self.currentScale)
matrix:Translate(Vector(
ScrW() * 0.5 - (ScrW() * self.currentScale * 0.5),
ScrH() * 0.5 - (ScrH() * self.currentScale * 0.5),
1
))
cam.PushModelMatrix(matrix)
self.currentMatrix = matrix
end
BaseClass.Paint(self, width, height)
if (bShouldScale) then
cam.PopModelMatrix()
self.currentMatrix = nil
end
if (amount > 0) then
local color = Color(0, 0, 0, amount)
surface.SetDrawColor(color)
surface.DrawRect(0, 0, width, height)
end
end
vgui.Register("ixCharMenuPanel", PANEL, "ixSubpanelParent")
-- character menu main button list
PANEL = {}
function PANEL:Init()
local parent = self:GetParent()
self:SetSize(parent:GetWide() * 0.25, parent:GetTall())
self:GetVBar():SetWide(0)
self:GetVBar():SetVisible(false)
end
function PANEL:Add(name)
local panel = vgui.Create(name, self)
panel:Dock(TOP)
return panel
end
function PANEL:SizeToContents()
self:GetCanvas():InvalidateLayout(true)
-- if the canvas has extra space, forcefully dock to the bottom so it doesn't anchor to the top
if (self:GetTall() > self:GetCanvas():GetTall()) then
self:GetCanvas():Dock(BOTTOM)
else
self:GetCanvas():Dock(NODOCK)
end
end
vgui.Register("ixCharMenuButtonList", PANEL, "DScrollPanel")
-- main character menu panel
PANEL = {}
AccessorFunc(PANEL, "bUsingCharacter", "UsingCharacter", FORCE_BOOL)
function PANEL:Init()
local parent = self:GetParent()
local padding = self:GetPadding()
local halfWidth = ScrW() * 0.5
local halfPadding = padding * 0.5
local bHasCharacter = #ix.characters > 0
self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
self:DockPadding(padding, padding, padding, padding)
local infoLabel = self:Add("DLabel")
infoLabel:SetTextColor(Color(255, 255, 255, 25))
infoLabel:SetFont("ixMenuMiniFont")
infoLabel:SetText(L("helix") .. " " .. GAMEMODE.Version)
infoLabel:SizeToContents()
infoLabel:SetPos(ScrW() - infoLabel:GetWide() - 4, ScrH() - infoLabel:GetTall() - 4)
local logoPanel = self:Add("Panel")
logoPanel:SetSize(ScrW(), ScrH() * 0.25)
logoPanel:SetPos(0, ScrH() * 0.25)
logoPanel.Paint = function(panel, width, height)
local matrix = self.currentMatrix
-- don't scale the background because it fucks the blur
if (matrix) then
cam.PopModelMatrix()
end
local newHeight = Lerp(1 - (self.currentDimAmount / 255), 0, height)
local y = height * 0.5 - newHeight * 0.5
local _, screenY = panel:LocalToScreen(0, 0)
screenY = screenY + y
render.SetScissorRect(0, screenY, width, screenY + newHeight, true)
ix.util.DrawBlur(panel, 15, nil, 200)
-- background dim
surface.SetDrawColor(0, 0, 0, 100)
surface.DrawRect(0, y, width, newHeight)
-- border lines
surface.SetDrawColor(ix.config.Get("color") or color_white)
surface.DrawRect(0, y, width, 1)
surface.DrawRect(0, y + newHeight - 1, width, 1)
if (matrix) then
cam.PushModelMatrix(matrix)
end
for _, v in ipairs(panel:GetChildren()) do
v:PaintManual()
end
render.SetScissorRect(0, 0, 0, 0, false)
end
-- draw schema logo material instead of text if available
local logo = Schema.logo and ix.util.GetMaterial(Schema.logo)
if (logo and !logo:IsError()) then
local logoImage = logoPanel:Add("DImage")
logoImage:SetMaterial(logo)
logoImage:SetSize(halfWidth, halfWidth * logo:Height() / logo:Width())
logoImage:SetPos(halfWidth - logoImage:GetWide() * 0.5, halfPadding)
logoImage:SetPaintedManually(true)
logoPanel:SetTall(logoImage:GetTall() + padding)
else
local newHeight = padding
local subtitle = L2("schemaDesc") or Schema.description
local titleLabel = logoPanel:Add("DLabel")
titleLabel:SetTextColor(color_white)
titleLabel:SetFont("ixTitleFont")
titleLabel:SetText(L2("schemaName") or Schema.name or L"unknown")
titleLabel:SizeToContents()
titleLabel:SetPos(halfWidth - titleLabel:GetWide() * 0.5, halfPadding)
titleLabel:SetPaintedManually(true)
newHeight = newHeight + titleLabel:GetTall()
if (subtitle) then
local subtitleLabel = logoPanel:Add("DLabel")
subtitleLabel:SetTextColor(color_white)
subtitleLabel:SetFont("ixSubTitleFont")
subtitleLabel:SetText(subtitle)
subtitleLabel:SizeToContents()
subtitleLabel:SetPos(halfWidth - subtitleLabel:GetWide() * 0.5, 0)
subtitleLabel:MoveBelow(titleLabel)
subtitleLabel:SetPaintedManually(true)
newHeight = newHeight + subtitleLabel:GetTall()
end
logoPanel:SetTall(newHeight)
end
-- button list
self.mainButtonList = self:Add("ixCharMenuButtonList")
self.mainButtonList:Dock(LEFT)
-- create character button
local createButton = self.mainButtonList:Add("ixMenuButton")
createButton:SetText("create")
createButton:SizeToContents()
createButton.DoClick = function()
local maximum = hook.Run("GetMaxPlayerCharacter", LocalPlayer()) or ix.config.Get("maxCharacters", 5)
-- don't allow creation if we've hit the character limit
if (#ix.characters >= maximum) then
self:GetParent():ShowNotice(3, L("maxCharacters"))
return
end
self:Dim()
parent.newCharacterPanel:SetActiveSubpanel("faction", 0)
parent.newCharacterPanel:SlideUp()
end
-- load character button
self.loadButton = self.mainButtonList:Add("ixMenuButton")
self.loadButton:SetText("load")
self.loadButton:SizeToContents()
self.loadButton.DoClick = function()
self:Dim()
parent.loadCharacterPanel:SlideUp()
end
if (!bHasCharacter) then
self.loadButton:SetDisabled(true)
end
-- community button
local extraURL = ix.config.Get("communityURL", "")
local extraText = ix.config.Get("communityText", "@community")
if (extraURL != "" and extraText != "") then
if (extraText:sub(1, 1) == "@") then
extraText = L(extraText:sub(2))
end
local extraButton = self.mainButtonList:Add("ixMenuButton")
extraButton:SetText(extraText, true)
extraButton:SizeToContents()
extraButton.DoClick = function()
gui.OpenURL(extraURL)
end
end
-- leave/return button
self.returnButton = self.mainButtonList:Add("ixMenuButton")
self:UpdateReturnButton()
self.returnButton.DoClick = function()
if (self.bUsingCharacter) then
parent:Close()
else
RunConsoleCommand("disconnect")
end
end
self.mainButtonList:SizeToContents()
end
function PANEL:UpdateReturnButton(bValue)
if (bValue != nil) then
self.bUsingCharacter = bValue
end
self.returnButton:SetText(self.bUsingCharacter and "return" or "leave")
self.returnButton:SizeToContents()
end
function PANEL:OnDim()
-- disable input on this panel since it will still be in the background while invisible - prone to stray clicks if the
-- panels overtop slide out of the way
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
end
function PANEL:OnUndim()
self:SetMouseInputEnabled(true)
self:SetKeyboardInputEnabled(true)
-- we may have just deleted a character so update the status of the return button
self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
self:UpdateReturnButton()
end
function PANEL:OnClose()
for _, v in pairs(self:GetChildren()) do
if (IsValid(v)) then
v:SetVisible(false)
end
end
end
function PANEL:PerformLayout(width, height)
local padding = self:GetPadding()
self.mainButtonList:SetPos(padding, height - self.mainButtonList:GetTall() - padding)
end
vgui.Register("ixCharMenuMain", PANEL, "ixCharMenuPanel")
-- container panel
PANEL = {}
function PANEL:Init()
if (IsValid(ix.gui.loading)) then
ix.gui.loading:Remove()
end
if (IsValid(ix.gui.characterMenu)) then
if (IsValid(ix.gui.characterMenu.channel)) then
ix.gui.characterMenu.channel:Stop()
end
ix.gui.characterMenu:Remove()
end
self:SetSize(ScrW(), ScrH())
self:SetPos(0, 0)
-- main menu panel
self.mainPanel = self:Add("ixCharMenuMain")
-- new character panel
self.newCharacterPanel = self:Add("ixCharMenuNew")
self.newCharacterPanel:SlideDown(0)
-- load character panel
self.loadCharacterPanel = self:Add("ixCharMenuLoad")
self.loadCharacterPanel:SlideDown(0)
-- notice bar
self.notice = self:Add("ixNoticeBar")
-- finalization
self:MakePopup()
self.currentAlpha = 255
self.volume = 0
ix.gui.characterMenu = self
if (!IsValid(ix.gui.intro)) then
self:PlayMusic()
end
hook.Run("OnCharacterMenuCreated", self)
end
function PANEL:PlayMusic()
local path = "sound/" .. ix.config.Get("music")
local url = path:match("http[s]?://.+")
local play = url and sound.PlayURL or sound.PlayFile
path = url and url or path
play(path, "noplay", function(channel, error, message)
if (!IsValid(self) or !IsValid(channel)) then
return
end
channel:SetVolume(self.volume or 0)
channel:Play()
self.channel = channel
self:CreateAnimation(audioFadeInTime, {
index = 10,
target = {volume = 1},
Think = function(animation, panel)
if (IsValid(panel.channel)) then
panel.channel:SetVolume(self.volume * 0.5)
end
end
})
end)
end
function PANEL:ShowNotice(type, text)
self.notice:SetType(type)
self.notice:SetText(text)
self.notice:Show()
end
function PANEL:HideNotice()
if (IsValid(self.notice) and !self.notice:GetHidden()) then
self.notice:Slide("up", 0.5, true)
end
end
function PANEL:OnCharacterDeleted(character)
if (#ix.characters == 0) then
self.mainPanel.loadButton:SetDisabled(true)
self.mainPanel:Undim() -- undim since the load panel will slide down
else
self.mainPanel.loadButton:SetDisabled(false)
end
self.loadCharacterPanel:OnCharacterDeleted(character)
end
function PANEL:OnCharacterLoadFailed(error)
self.loadCharacterPanel:SetMouseInputEnabled(true)
self.loadCharacterPanel:SlideUp()
self:ShowNotice(3, error)
end
function PANEL:IsClosing()
return self.bClosing
end
function PANEL:Close(bFromMenu)
self.bClosing = true
self.bFromMenu = bFromMenu
local fadeOutTime = animationTime * 8
self:CreateAnimation(fadeOutTime, {
index = 1,
target = {currentAlpha = 0},
Think = function(animation, panel)
panel:SetAlpha(panel.currentAlpha)
end,
OnComplete = function(animation, panel)
panel:Remove()
end
})
self:CreateAnimation(fadeOutTime - 0.1, {
index = 10,
target = {volume = 0},
Think = function(animation, panel)
if (IsValid(panel.channel)) then
panel.channel:SetVolume(self.volume * 0.5)
end
end,
OnComplete = function(animation, panel)
if (IsValid(panel.channel)) then
panel.channel:Stop()
panel.channel = nil
end
end
})
-- hide children if we're already dimmed
if (bFromMenu) then
for _, v in pairs(self:GetChildren()) do
if (IsValid(v)) then
v:SetVisible(false)
end
end
else
-- fade out the main panel quicker because it significantly blocks the screen
self.mainPanel.currentAlpha = 255
self.mainPanel:CreateAnimation(animationTime * 2, {
target = {currentAlpha = 0},
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(panel.currentAlpha)
end,
OnComplete = function(animation, panel)
panel:SetVisible(false)
end
})
end
-- relinquish mouse control
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
gui.EnableScreenClicker(false)
end
function PANEL:Paint(width, height)
surface.SetTexture(gradient)
surface.SetDrawColor(0, 0, 0, 255)
surface.DrawTexturedRect(0, 0, width, height)
if (!ix.option.Get("cheapBlur", false)) then
surface.SetDrawColor(0, 0, 0, 150)
surface.DrawTexturedRect(0, 0, width, height)
ix.util.DrawBlur(self, Lerp((self.currentAlpha - 200) / 255, 0, 10))
end
end
function PANEL:PaintOver(width, height)
if (self.bClosing and self.bFromMenu) then
surface.SetDrawColor(color_black)
surface.DrawRect(0, 0, width, height)
end
end
function PANEL:OnRemove()
if (IsValid(self.channel)) then
self.channel:Stop()
self.channel = nil
end
end
vgui.Register("ixCharMenu", PANEL, "EditablePanel")
if (IsValid(ix.gui.characterMenu)) then
ix.gui.characterMenu:Remove()
--TODO: REMOVE ME
ix.gui.characterMenu = vgui.Create("ixCharMenu")
end
================================================
FILE: gamemode/core/derma/cl_charcreate.lua
================================================
local padding = ScreenScale(32)
-- create character panel
DEFINE_BASECLASS("ixCharMenuPanel")
local PANEL = {}
function PANEL:Init()
local parent = self:GetParent()
local halfWidth = parent:GetWide() * 0.5 - (padding * 2)
local halfHeight = parent:GetTall() * 0.5 - (padding * 2)
local modelFOV = (ScrW() > ScrH() * 1.8) and 100 or 78
self:ResetPayload(true)
self.factionButtons = {}
self.repopulatePanels = {}
-- faction selection subpanel
self.factionPanel = self:AddSubpanel("faction", true)
self.factionPanel:SetTitle("chooseFaction")
self.factionPanel.OnSetActive = function()
-- if we only have one faction, we are always selecting that one so we can skip to the description section
if (#self.factionButtons == 1) then
self:SetActiveSubpanel("description", 0)
end
end
local modelList = self.factionPanel:Add("Panel")
modelList:Dock(RIGHT)
modelList:SetSize(halfWidth + padding * 2, halfHeight)
local proceed = modelList:Add("ixMenuButton")
proceed:SetText("proceed")
proceed:SetContentAlignment(6)
proceed:Dock(BOTTOM)
proceed:SizeToContents()
proceed.DoClick = function()
self.progress:IncrementProgress()
self:Populate()
self:SetActiveSubpanel("description")
end
self.factionModel = modelList:Add("ixModelPanel")
self.factionModel:Dock(FILL)
self.factionModel:SetModel("models/error.mdl")
self.factionModel:SetFOV(modelFOV)
self.factionModel.PaintModel = self.factionModel.Paint
self.factionButtonsPanel = self.factionPanel:Add("ixCharMenuButtonList")
self.factionButtonsPanel:SetWide(halfWidth)
self.factionButtonsPanel:Dock(FILL)
local factionBack = self.factionPanel:Add("ixMenuButton")
factionBack:SetText("return")
factionBack:SizeToContents()
factionBack:Dock(BOTTOM)
factionBack.DoClick = function()
self.progress:DecrementProgress()
self:SetActiveSubpanel("faction", 0)
self:SlideDown()
parent.mainPanel:Undim()
end
-- character customization subpanel
self.description = self:AddSubpanel("description")
self.description:SetTitle("chooseDescription")
local descriptionModelList = self.description:Add("Panel")
descriptionModelList:Dock(LEFT)
descriptionModelList:SetSize(halfWidth, halfHeight)
local descriptionBack = descriptionModelList:Add("ixMenuButton")
descriptionBack:SetText("return")
descriptionBack:SetContentAlignment(4)
descriptionBack:SizeToContents()
descriptionBack:Dock(BOTTOM)
descriptionBack.DoClick = function()
self.progress:DecrementProgress()
if (#self.factionButtons == 1) then
factionBack:DoClick()
else
self:SetActiveSubpanel("faction")
end
end
self.descriptionModel = descriptionModelList:Add("ixModelPanel")
self.descriptionModel:Dock(FILL)
self.descriptionModel:SetModel(self.factionModel:GetModel())
self.descriptionModel:SetFOV(modelFOV - 13)
self.descriptionModel.PaintModel = self.descriptionModel.Paint
self.descriptionPanel = self.description:Add("Panel")
self.descriptionPanel:SetWide(halfWidth + padding * 2)
self.descriptionPanel:Dock(RIGHT)
local descriptionProceed = self.descriptionPanel:Add("ixMenuButton")
descriptionProceed:SetText("proceed")
descriptionProceed:SetContentAlignment(6)
descriptionProceed:SizeToContents()
descriptionProceed:Dock(BOTTOM)
descriptionProceed.DoClick = function()
if (self:VerifyProgression("description")) then
-- there are no panels on the attributes section other than the create button, so we can just create the character
if (#self.attributesPanel:GetChildren() < 2) then
self:SendPayload()
return
end
self.progress:IncrementProgress()
self:SetActiveSubpanel("attributes")
end
end
-- attributes subpanel
self.attributes = self:AddSubpanel("attributes")
self.attributes:SetTitle("chooseSkills")
local attributesModelList = self.attributes:Add("Panel")
attributesModelList:Dock(LEFT)
attributesModelList:SetSize(halfWidth, halfHeight)
local attributesBack = attributesModelList:Add("ixMenuButton")
attributesBack:SetText("return")
attributesBack:SetContentAlignment(4)
attributesBack:SizeToContents()
attributesBack:Dock(BOTTOM)
attributesBack.DoClick = function()
self.progress:DecrementProgress()
self:SetActiveSubpanel("description")
end
self.attributesModel = attributesModelList:Add("ixModelPanel")
self.attributesModel:Dock(FILL)
self.attributesModel:SetModel(self.factionModel:GetModel())
self.attributesModel:SetFOV(modelFOV - 13)
self.attributesModel.PaintModel = self.attributesModel.Paint
self.attributesPanel = self.attributes:Add("Panel")
self.attributesPanel:SetWide(halfWidth + padding * 2)
self.attributesPanel:Dock(RIGHT)
local create = self.attributesPanel:Add("ixMenuButton")
create:SetText("finish")
create:SetContentAlignment(6)
create:SizeToContents()
create:Dock(BOTTOM)
create.DoClick = function()
self:SendPayload()
end
-- creation progress panel
self.progress = self:Add("ixSegmentedProgress")
self.progress:SetBarColor(ix.config.Get("color"))
self.progress:SetSize(parent:GetWide(), 0)
self.progress:SizeToContents()
self.progress:SetPos(0, parent:GetTall() - self.progress:GetTall())
-- setup payload hooks
self:AddPayloadHook("model", function(value)
local faction = ix.faction.indices[self.payload.faction]
if (faction) then
local model = faction:GetModels(LocalPlayer())[value]
-- assuming bodygroups
if (istable(model)) then
self.factionModel:SetModel(model[1], model[2] or 0, model[3])
self.descriptionModel:SetModel(model[1], model[2] or 0, model[3])
self.attributesModel:SetModel(model[1], model[2] or 0, model[3])
else
self.factionModel:SetModel(model)
self.descriptionModel:SetModel(model)
self.attributesModel:SetModel(model)
end
end
end)
-- setup character creation hooks
net.Receive("ixCharacterAuthed", function()
timer.Remove("ixCharacterCreateTimeout")
self.awaitingResponse = false
local id = net.ReadUInt(32)
local indices = net.ReadUInt(6)
local charList = {}
for _ = 1, indices do
charList[#charList + 1] = net.ReadUInt(32)
end
ix.characters = charList
self:SlideDown()
if (!IsValid(self) or !IsValid(parent)) then
return
end
if (LocalPlayer():GetCharacter()) then
parent.mainPanel:Undim()
parent:ShowNotice(2, L("charCreated"))
elseif (id) then
self.bMenuShouldClose = true
net.Start("ixCharacterChoose")
net.WriteUInt(id, 32)
net.SendToServer()
else
self:SlideDown()
end
end)
net.Receive("ixCharacterAuthFailed", function()
timer.Remove("ixCharacterCreateTimeout")
self.awaitingResponse = false
local fault = net.ReadString()
local args = net.ReadTable()
self:SlideDown()
parent.mainPanel:Undim()
parent:ShowNotice(3, L(fault, unpack(args)))
end)
end
function PANEL:SendPayload()
if (self.awaitingResponse or !self:VerifyProgression()) then
return
end
self.awaitingResponse = true
timer.Create("ixCharacterCreateTimeout", 10, 1, function()
if (IsValid(self) and self.awaitingResponse) then
local parent = self:GetParent()
self.awaitingResponse = false
self:SlideDown()
parent.mainPanel:Undim()
parent:ShowNotice(3, L("unknownError"))
end
end)
self.payload:Prepare()
net.Start("ixCharacterCreate")
net.WriteUInt(table.Count(self.payload), 8)
for k, v in pairs(self.payload) do
net.WriteString(k)
net.WriteType(v)
end
net.SendToServer()
end
function PANEL:OnSlideUp()
self:ResetPayload()
self:Populate()
self.progress:SetProgress(1)
-- the faction subpanel will skip to next subpanel if there is only one faction to choose from,
-- so we don't have to worry about it here
self:SetActiveSubpanel("faction", 0)
end
function PANEL:OnSlideDown()
end
function PANEL:ResetPayload(bWithHooks)
if (bWithHooks) then
self.hooks = {}
end
self.payload = {}
-- TODO: eh..
function self.payload.Set(payload, key, value)
self:SetPayload(key, value)
end
function self.payload.AddHook(payload, key, callback)
self:AddPayloadHook(key, callback)
end
function self.payload.Prepare(payload)
self.payload.Set = nil
self.payload.AddHook = nil
self.payload.Prepare = nil
end
end
function PANEL:SetPayload(key, value)
self.payload[key] = value
self:RunPayloadHook(key, value)
end
function PANEL:AddPayloadHook(key, callback)
if (!self.hooks[key]) then
self.hooks[key] = {}
end
self.hooks[key][#self.hooks[key] + 1] = callback
end
function PANEL:RunPayloadHook(key, value)
local hooks = self.hooks[key] or {}
for _, v in ipairs(hooks) do
v(value)
end
end
function PANEL:GetContainerPanel(name)
-- TODO: yuck
if (name == "description") then
return self.descriptionPanel
elseif (name == "attributes") then
return self.attributesPanel
end
return self.descriptionPanel
end
function PANEL:AttachCleanup(panel)
self.repopulatePanels[#self.repopulatePanels + 1] = panel
end
function PANEL:Populate()
if (!self.bInitialPopulate) then
-- setup buttons for the faction panel
-- TODO: make this a bit less janky
local lastSelected
for _, v in pairs(self.factionButtons) do
if (v:GetSelected()) then
lastSelected = v.faction
end
if (IsValid(v)) then
v:Remove()
end
end
self.factionButtons = {}
for _, v in SortedPairs(ix.faction.teams) do
if (ix.faction.HasWhitelist(v.index)) then
local button = self.factionButtonsPanel:Add("ixMenuSelectionButton")
button:SetBackgroundColor(v.color or color_white)
button:SetText(L(v.name):utf8upper())
button:SizeToContents()
button:SetButtonList(self.factionButtons)
button.faction = v.index
button.OnSelected = function(panel)
local faction = ix.faction.indices[panel.faction]
local models = faction:GetModels(LocalPlayer())
self.payload:Set("faction", panel.faction)
self.payload:Set("model", math.random(1, #models))
end
if ((lastSelected and lastSelected == v.index) or (!lastSelected and v.isDefault)) then
button:SetSelected(true)
lastSelected = v.index
end
end
end
end
-- remove panels created for character vars
for i = 1, #self.repopulatePanels do
self.repopulatePanels[i]:Remove()
end
self.repopulatePanels = {}
-- payload is empty because we attempted to send it - for whatever reason we're back here again so we need to repopulate
if (!self.payload.faction) then
for _, v in pairs(self.factionButtons) do
if (v:GetSelected()) then
v:SetSelected(true)
break
end
end
end
self.factionButtonsPanel:SizeToContents()
local zPos = 1
-- set up character vars
for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do
if (!v.bNoDisplay and k != "__SortedIndex") then
local container = self:GetContainerPanel(v.category or "description")
if (v.ShouldDisplay and v:ShouldDisplay(container, self.payload) == false) then
continue
end
local panel
-- if the var has a custom way of displaying, we'll use that instead
if (v.OnDisplay) then
panel = v:OnDisplay(container, self.payload)
elseif (isstring(v.default)) then
panel = container:Add("ixTextEntry")
panel:Dock(TOP)
panel:SetFont("ixMenuButtonHugeFont")
panel:SetUpdateOnType(true)
panel.OnValueChange = function(this, text)
self.payload:Set(k, text)
end
end
if (IsValid(panel)) then
-- add label for entry
local label = container:Add("DLabel")
label:SetFont("ixMenuButtonLabelFont")
label:SetText(L(k):utf8upper())
label:SizeToContents()
label:DockMargin(0, 16, 0, 2)
label:Dock(TOP)
-- we need to set the docking order so the label is above the panel
label:SetZPos(zPos - 1)
panel:SetZPos(zPos)
self:AttachCleanup(label)
self:AttachCleanup(panel)
if (v.OnPostSetup) then
v:OnPostSetup(panel, self.payload)
end
zPos = zPos + 2
end
end
end
if (!self.bInitialPopulate) then
-- setup progress bar segments
if (#self.factionButtons > 1) then
self.progress:AddSegment("@faction")
end
self.progress:AddSegment("@description")
if (#self.attributesPanel:GetChildren() > 1) then
self.progress:AddSegment("@skills")
end
-- we don't need to show the progress bar if there's only one segment
if (#self.progress:GetSegments() == 1) then
self.progress:SetVisible(false)
end
end
self.bInitialPopulate = true
end
function PANEL:VerifyProgression(name)
for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do
if (name ~= nil and (v.category or "description") != name) then
continue
end
local value = self.payload[k]
if (!v.bNoDisplay or v.OnValidate) then
if (v.OnValidate) then
local result = {v:OnValidate(value, self.payload, LocalPlayer())}
if (result[1] == false) then
self:GetParent():ShowNotice(3, L(unpack(result, 2)))
return false
end
end
self.payload[k] = value
end
end
return true
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintCharacterCreateBackground", self, width, height)
BaseClass.Paint(self, width, height)
end
vgui.Register("ixCharMenuNew", PANEL, "ixCharMenuPanel")
================================================
FILE: gamemode/core/derma/cl_charload.lua
================================================
local errorModel = "models/error.mdl"
local PANEL = {}
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
local function SetCharacter(self, character)
self.character = character
if (character) then
self:SetModel(character:GetModel())
self:SetSkin(character:GetData("skin", 0))
for i = 0, (self:GetNumBodyGroups() - 1) do
self:SetBodygroup(i, 0)
end
local bodygroups = character:GetData("groups", nil)
if (istable(bodygroups)) then
for k, v in pairs(bodygroups) do
self:SetBodygroup(k, v)
end
end
else
self:SetModel(errorModel)
end
end
local function GetCharacter(self)
return self.character
end
function PANEL:Init()
self.activeCharacter = ClientsideModel(errorModel)
self.activeCharacter:SetNoDraw(true)
self.activeCharacter.SetCharacter = SetCharacter
self.activeCharacter.GetCharacter = GetCharacter
self.lastCharacter = ClientsideModel(errorModel)
self.lastCharacter:SetNoDraw(true)
self.lastCharacter.SetCharacter = SetCharacter
self.lastCharacter.GetCharacter = GetCharacter
self.animationTime = 0.5
self.shadeY = 0
self.shadeHeight = 0
self.cameraPosition = Vector(80, 0, 35)
self.cameraAngle = Angle(0, 180, 0)
self.lastPaint = 0
end
function PANEL:ResetSequence(model, lastModel)
local sequence = model:LookupSequence("idle_unarmed")
if (sequence <= 0) then
sequence = model:SelectWeightedSequence(ACT_IDLE)
end
if (sequence > 0) then
model:ResetSequence(sequence)
else
local found = false
for _, v in ipairs(model:GetSequenceList()) do
if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then
model:ResetSequence(v)
found = true
break
end
end
if (!found) then
model:ResetSequence(4)
end
end
model:SetIK(false)
-- copy cycle if we can to avoid a jarring transition from resetting the sequence
if (lastModel) then
model:SetCycle(lastModel:GetCycle())
end
end
function PANEL:RunAnimation(model)
model:FrameAdvance((RealTime() - self.lastPaint) * 0.5)
end
function PANEL:LayoutEntity(model)
model:SetIK(false)
self:RunAnimation(model)
end
function PANEL:SetActiveCharacter(character)
self.shadeY = self:GetTall()
self.shadeHeight = self:GetTall()
-- set character immediately if we're an error (something isn't selected yet)
if (self.activeCharacter:GetModel() == errorModel) then
self.activeCharacter:SetCharacter(character)
self:ResetSequence(self.activeCharacter)
return
end
-- if the animation is already playing, we update its parameters so we can avoid restarting
local shade = self:GetTweenAnimation(1)
local shadeHide = self:GetTweenAnimation(2)
if (shade) then
shade.newCharacter = character
return
elseif (shadeHide) then
shadeHide.queuedCharacter = character
return
end
self.lastCharacter:SetCharacter(self.activeCharacter:GetCharacter())
self:ResetSequence(self.lastCharacter, self.activeCharacter)
shade = self:CreateAnimation(self.animationTime * 0.5, {
index = 1,
target = {
shadeY = 0,
shadeHeight = self:GetTall()
},
easing = "linear",
OnComplete = function(shadeAnimation, shadePanel)
shadePanel.activeCharacter:SetCharacter(shadeAnimation.newCharacter)
shadePanel:ResetSequence(shadePanel.activeCharacter)
shadePanel:CreateAnimation(shadePanel.animationTime, {
index = 2,
target = {shadeHeight = 0},
easing = "outQuint",
OnComplete = function(animation, panel)
if (animation.queuedCharacter) then
panel:SetActiveCharacter(animation.queuedCharacter)
else
panel.lastCharacter:SetCharacter(nil)
end
end
})
end
})
shade.newCharacter = character
end
function PANEL:Paint(width, height)
local x, y = self:LocalToScreen(0, 0)
local bTransition = self.lastCharacter:GetModel() != errorModel
local modelFOV = (ScrW() > ScrH() * 1.8) and 92 or 70
cam.Start3D(self.cameraPosition, self.cameraAngle, modelFOV, x, y, width, height)
render.SuppressEngineLighting(true)
render.SetLightingOrigin(self.activeCharacter:GetPos())
-- setup lighting
render.SetModelLighting(0, 1.5, 1.5, 1.5)
for i = 1, 4 do
render.SetModelLighting(i, 0.4, 0.4, 0.4)
end
render.SetModelLighting(5, 0.04, 0.04, 0.04)
-- clip anything out of bounds
local curparent = self
local rightx = self:GetWide()
local leftx = 0
local topy = 0
local bottomy = self:GetTall()
local previous = curparent
while (curparent:GetParent() != nil) do
local lastX, lastY = previous:GetPos()
curparent = curparent:GetParent()
topy = math.Max(lastY, topy + lastY)
leftx = math.Max(lastX, leftx + lastX)
bottomy = math.Min(lastY + previous:GetTall(), bottomy + lastY)
rightx = math.Min(lastX + previous:GetWide(), rightx + lastX)
previous = curparent
end
ix.util.ResetStencilValues()
render.SetStencilEnable(true)
render.SetStencilWriteMask(30)
render.SetStencilTestMask(30)
render.SetStencilReferenceValue(31)
render.SetStencilCompareFunction(STENCIL_ALWAYS)
render.SetStencilPassOperation(STENCIL_REPLACE)
render.SetStencilFailOperation(STENCIL_KEEP)
render.SetStencilZFailOperation(STENCIL_KEEP)
self:LayoutEntity(self.activeCharacter)
if (bTransition) then
-- only need to layout while it's used
self:LayoutEntity(self.lastCharacter)
render.SetScissorRect(leftx, topy, rightx, bottomy - (self:GetTall() - self.shadeHeight), true)
self.lastCharacter:DrawModel()
render.SetScissorRect(leftx, topy + self.shadeHeight, rightx, bottomy, true)
self.activeCharacter:DrawModel()
render.SetScissorRect(leftx, topy, rightx, bottomy, true)
else
self.activeCharacter:DrawModel()
end
render.SetStencilCompareFunction(STENCIL_EQUAL)
render.SetStencilPassOperation(STENCIL_KEEP)
cam.Start2D()
derma.SkinFunc("PaintCharacterTransitionOverlay", self, 0, self.shadeY, width, self.shadeHeight)
cam.End2D()
render.SetStencilEnable(false)
render.SetScissorRect(0, 0, 0, 0, false)
render.SuppressEngineLighting(false)
cam.End3D()
self.lastPaint = RealTime()
end
function PANEL:OnRemove()
self.lastCharacter:Remove()
self.activeCharacter:Remove()
end
vgui.Register("ixCharMenuCarousel", PANEL, "Panel")
-- character load panel
PANEL = {}
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
AccessorFunc(PANEL, "backgroundFraction", "BackgroundFraction", FORCE_NUMBER)
function PANEL:Init()
local parent = self:GetParent()
local padding = self:GetPadding()
local halfWidth = parent:GetWide() * 0.5 - (padding * 2)
local halfHeight = parent:GetTall() * 0.5 - (padding * 2)
local modelFOV = (ScrW() > ScrH() * 1.8) and 102 or 78
self.animationTime = 1
self.backgroundFraction = 1
-- main panel
self.panel = self:AddSubpanel("main")
self.panel:SetTitle("loadTitle")
self.panel.OnSetActive = function()
self:CreateAnimation(self.animationTime, {
index = 2,
target = {backgroundFraction = 1},
easing = "outQuint",
})
end
-- character button list
local controlList = self.panel:Add("Panel")
controlList:Dock(LEFT)
controlList:SetSize(halfWidth, halfHeight)
local back = controlList:Add("ixMenuButton")
back:Dock(BOTTOM)
back:SetText("return")
back:SizeToContents()
back.DoClick = function()
self:SlideDown()
parent.mainPanel:Undim()
end
self.characterList = controlList:Add("ixCharMenuButtonList")
self.characterList.buttons = {}
self.characterList:Dock(FILL)
-- right-hand side with carousel and buttons
local infoPanel = self.panel:Add("Panel")
infoPanel:Dock(FILL)
local infoButtons = infoPanel:Add("Panel")
infoButtons:Dock(BOTTOM)
infoButtons:SetTall(back:GetTall()) -- hmm...
local continueButton = infoButtons:Add("ixMenuButton")
continueButton:Dock(FILL)
continueButton:SetText("choose")
continueButton:SetContentAlignment(6)
continueButton:SizeToContents()
continueButton.DoClick = function()
self:SlideDown(self.animationTime, function()
net.Start("ixCharacterChoose")
net.WriteUInt(self.character:GetID(), 32)
net.SendToServer()
end, true)
end
local deleteButton = infoButtons:Add("ixMenuButton")
deleteButton:Dock(LEFT)
deleteButton:SetText("delete")
deleteButton:SetContentAlignment(5)
deleteButton:SetTextInset(0, 0)
deleteButton:SizeToContents()
deleteButton:SetTextColor(derma.GetColor("Error", deleteButton))
deleteButton.DoClick = function()
self:SetActiveSubpanel("delete")
end
self.carousel = infoPanel:Add("ixCharMenuCarousel")
self.carousel:Dock(FILL)
-- character deletion panel
self.delete = self:AddSubpanel("delete")
self.delete:SetTitle(nil)
self.delete.OnSetActive = function()
self.deleteModel:SetModel(self.character:GetModel())
self:CreateAnimation(self.animationTime, {
index = 2,
target = {backgroundFraction = 0},
easing = "outQuint"
})
end
local deleteInfo = self.delete:Add("Panel")
deleteInfo:SetSize(parent:GetWide() * 0.5, parent:GetTall())
deleteInfo:Dock(LEFT)
local deleteReturn = deleteInfo:Add("ixMenuButton")
deleteReturn:Dock(BOTTOM)
deleteReturn:SetText("no")
deleteReturn:SizeToContents()
deleteReturn.DoClick = function()
self:SetActiveSubpanel("main")
end
local deleteConfirm = self.delete:Add("ixMenuButton")
deleteConfirm:Dock(BOTTOM)
deleteConfirm:SetText("yes")
deleteConfirm:SetContentAlignment(6)
deleteConfirm:SizeToContents()
deleteConfirm:SetTextColor(derma.GetColor("Error", deleteConfirm))
deleteConfirm.DoClick = function()
local id = self.character:GetID()
parent:ShowNotice(1, L("deleteComplete", self.character:GetName()))
self:Populate(id)
self:SetActiveSubpanel("main")
net.Start("ixCharacterDelete")
net.WriteUInt(id, 32)
net.SendToServer()
end
self.deleteModel = deleteInfo:Add("ixModelPanel")
self.deleteModel:Dock(FILL)
self.deleteModel:SetModel(errorModel)
self.deleteModel:SetFOV(modelFOV)
self.deleteModel.PaintModel = self.deleteModel.Paint
local deleteNag = self.delete:Add("Panel")
deleteNag:SetTall(parent:GetTall() * 0.5)
deleteNag:Dock(BOTTOM)
local deleteTitle = deleteNag:Add("DLabel")
deleteTitle:SetFont("ixTitleFont")
deleteTitle:SetText(L("areYouSure"):utf8upper())
deleteTitle:SetTextColor(ix.config.Get("color"))
deleteTitle:SizeToContents()
deleteTitle:Dock(TOP)
local deleteText = deleteNag:Add("DLabel")
deleteText:SetFont("ixMenuButtonFont")
deleteText:SetText(L("deleteConfirm"))
deleteText:SetTextColor(color_white)
deleteText:SetContentAlignment(7)
deleteText:Dock(FILL)
-- finalize setup
self:SetActiveSubpanel("main", 0)
end
function PANEL:OnCharacterDeleted(character)
if (self.bActive and #ix.characters == 0) then
self:SlideDown()
end
end
function PANEL:Populate(ignoreID)
self.characterList:Clear()
self.characterList.buttons = {}
local bSelected
-- loop backwards to preserve order since we're docking to the bottom
for i = 1, #ix.characters do
local id = ix.characters[i]
local character = ix.char.loaded[id]
if (!character or character:GetID() == ignoreID) then
continue
end
local index = character:GetFaction()
local faction = ix.faction.indices[index]
local color = faction and faction.color or color_white
local button = self.characterList:Add("ixMenuSelectionButton")
button:SetBackgroundColor(color)
button:SetText(character:GetName())
button:SizeToContents()
button:SetButtonList(self.characterList.buttons)
button.character = character
button.OnSelected = function(panel)
self:OnCharacterButtonSelected(panel)
end
-- select currently loaded character if available
local localCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
if (localCharacter and character:GetID() == localCharacter:GetID()) then
button:SetSelected(true)
self.characterList:ScrollToChild(button)
bSelected = true
end
end
if (!bSelected) then
local buttons = self.characterList.buttons
if (#buttons > 0) then
local button = buttons[#buttons]
button:SetSelected(true)
self.characterList:ScrollToChild(button)
else
self.character = nil
end
end
self.characterList:SizeToContents()
end
function PANEL:OnSlideUp()
self.bActive = true
self:Populate()
end
function PANEL:OnSlideDown()
self.bActive = false
end
function PANEL:OnCharacterButtonSelected(panel)
self.carousel:SetActiveCharacter(panel.character)
self.character = panel.character
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintCharacterLoadBackground", self, width, height)
end
vgui.Register("ixCharMenuLoad", PANEL, "ixCharMenuPanel")
================================================
FILE: gamemode/core/derma/cl_classes.lua
================================================
local PANEL = {}
function PANEL:Init()
self:SetTall(64)
local function AssignClick(panel)
panel.OnMousePressed = function()
self.pressing = -1
self:OnClick()
end
panel.OnMouseReleased = function()
if (self.pressing) then
self.pressing = nil
end
end
end
self.icon = self:Add("SpawnIcon")
self.icon:SetSize(128, 64)
self.icon:InvalidateLayout(true)
self.icon:Dock(LEFT)
self.icon.PaintOver = function(this, w, h)
end
AssignClick(self.icon)
self.limit = self:Add("DLabel")
self.limit:Dock(RIGHT)
self.limit:SetMouseInputEnabled(true)
self.limit:SetCursor("hand")
self.limit:SetExpensiveShadow(1, Color(0, 0, 60))
self.limit:SetContentAlignment(5)
self.limit:SetFont("ixMediumFont")
self.limit:SetWide(64)
AssignClick(self.limit)
self.label = self:Add("DLabel")
self.label:Dock(FILL)
self.label:SetMouseInputEnabled(true)
self.label:SetCursor("hand")
self.label:SetExpensiveShadow(1, Color(0, 0, 60))
self.label:SetContentAlignment(5)
self.label:SetFont("ixMediumFont")
AssignClick(self.label)
end
function PANEL:OnClick()
ix.command.Send("BecomeClass", self.class)
end
function PANEL:SetNumber(number)
local limit = self.data.limit
if (limit > 0) then
self.limit:SetText(Format("%s/%s", number, limit))
else
self.limit:SetText("∞")
end
end
function PANEL:SetClass(data)
if (data.model) then
local model = data.model
if (istable(model)) then
model = model[ math.random(#model) ]
end
self.icon:SetModel(model)
else
local char = LocalPlayer():GetCharacter()
local model = LocalPlayer():GetModel()
if (char) then
model = char:GetModel()
end
self.icon:SetModel(model)
end
self.label:SetText(L(data.name))
self.data = data
self.class = data.index
self:SetNumber(#ix.class.GetPlayers(data.index))
end
vgui.Register("ixClassPanel", PANEL, "DPanel")
PANEL = {}
function PANEL:Init()
ix.gui.classes = self
self:SetSize(self:GetParent():GetSize())
self.list = vgui.Create("DPanelList", self)
self.list:Dock(FILL)
self.list:EnableVerticalScrollbar()
self.list:SetSpacing(5)
self.list:SetPadding(5)
self.classPanels = {}
self:LoadClasses()
end
function PANEL:LoadClasses()
self.list:Clear()
for k, v in ipairs(ix.class.list) do
local no, why = ix.class.CanSwitchTo(LocalPlayer(), k)
local itsFull = ("class is full" == why)
if (no or itsFull) then
local panel = vgui.Create("ixClassPanel", self.list)
panel:SetClass(v)
table.insert(self.classPanels, panel)
self.list:AddItem(panel)
end
end
end
vgui.Register("ixClasses", PANEL, "EditablePanel")
hook.Add("CreateMenuButtons", "ixClasses", function(tabs)
local cnt = table.Count(ix.class.list)
if (cnt <= 1) then return end
for k, _ in ipairs(ix.class.list) do
if (!ix.class.CanSwitchTo(LocalPlayer(), k)) then
continue
else
tabs["classes"] = function(container)
container:Add("ixClasses")
end
return
end
end
end)
net.Receive("ixClassUpdate", function()
local client = net.ReadEntity()
if (ix.gui.classes and ix.gui.classes:IsVisible()) then
if (client == LocalPlayer()) then
ix.gui.classes:LoadClasses()
else
for _, v in ipairs(ix.gui.classes.classPanels) do
local data = v.data
v:SetNumber(#ix.class.GetPlayers(data.index))
end
end
end
end)
================================================
FILE: gamemode/core/derma/cl_config.lua
================================================
-- config manager
local PANEL = {}
function PANEL:Init()
self:Dock(FILL)
self:SetSearchEnabled(true)
self:Populate()
end
function PANEL:Populate()
-- gather categories
local categories = {}
local categoryIndices = {}
for k, v in pairs(ix.config.stored) do
local index = v.data and v.data.category or "misc"
categories[index] = categories[index] or {}
categories[index][k] = v
end
-- sort by category phrase
for k, _ in pairs(categories) do
categoryIndices[#categoryIndices + 1] = k
end
table.sort(categoryIndices, function(a, b)
return L(a) < L(b)
end)
-- add panels
for _, category in ipairs(categoryIndices) do
local categoryPhrase = L(category)
self:AddCategory(categoryPhrase)
-- we can use sortedpairs since configs don't have phrases to account for
for k, v in SortedPairs(categories[category]) do
if (isfunction(v.hidden) and v.hidden()) then
continue
end
local data = v.data.data
local type = v.type
local value = ix.util.SanitizeType(type, ix.config.Get(k))
local row = self:AddRow(type, categoryPhrase)
row:SetText(ix.util.ExpandCamelCase(k))
row:Populate(k, v.data)
-- type-specific properties
if (type == ix.type.number) then
row:SetMin(data and data.min or 0)
row:SetMax(data and data.max or 1)
row:SetDecimals(data and data.decimals or 0)
end
row:SetValue(value, true)
row:SetShowReset(value != v.default, k, v.default)
row.OnValueChanged = function(panel)
local newValue = ix.util.SanitizeType(type, panel:GetValue())
panel:SetShowReset(newValue != v.default, k, v.default)
net.Start("ixConfigSet")
net.WriteString(k)
net.WriteType(newValue)
net.SendToServer()
end
row.OnResetClicked = function(panel)
panel:SetValue(v.default, true)
panel:SetShowReset(false)
net.Start("ixConfigSet")
net.WriteString(k)
net.WriteType(v.default)
net.SendToServer()
end
row:GetLabel():SetHelixTooltip(function(tooltip)
local title = tooltip:AddRow("name")
title:SetImportant()
title:SetText(k)
title:SizeToContents()
title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5))
local description = tooltip:AddRow("description")
description:SetText(v.description)
description:SizeToContents()
end)
end
end
self:SizeToContents()
end
vgui.Register("ixConfigManager", PANEL, "ixSettings")
-- plugin manager
PANEL = {}
function PANEL:Init()
self:Dock(FILL)
self:SetSearchEnabled(true)
self.loadedCategory = L("loadedPlugins")
self.unloadedCategory = L("unloadedPlugins")
if (!ix.gui.bReceivedUnloadedPlugins) then
net.Start("ixConfigRequestUnloadedList")
net.SendToServer()
end
self:Populate()
end
function PANEL:OnPluginToggled(uniqueID, bEnabled)
net.Start("ixConfigPluginToggle")
net.WriteString(uniqueID)
net.WriteBool(bEnabled)
net.SendToServer()
end
function PANEL:Populate()
self:AddCategory(self.loadedCategory)
self:AddCategory(self.unloadedCategory)
-- add loaded plugins
for k, v in SortedPairsByMemberValue(ix.plugin.list, "name") do
local row = self:AddRow(ix.type.bool, self.loadedCategory)
row.id = k
row.setting:SetEnabledText(L("on"):utf8upper())
row.setting:SetDisabledText(L("off"):utf8upper())
row.setting:SizeToContents()
-- if this plugin is not in the unloaded list currently, then it's queued for an unload
row:SetValue(!ix.plugin.unloaded[k], true)
row:SetText(v.name)
row.OnValueChanged = function(panel, bEnabled)
self:OnPluginToggled(k, bEnabled)
end
row:GetLabel():SetHelixTooltip(function(tooltip)
local title = tooltip:AddRow("name")
title:SetImportant()
title:SetText(v.name)
title:SizeToContents()
title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5))
local description = tooltip:AddRow("description")
description:SetText(v.description)
description:SizeToContents()
end)
end
self:UpdateUnloaded(true)
self:SizeToContents()
end
function PANEL:UpdatePlugin(uniqueID, bEnabled)
for _, v in pairs(self:GetRows()) do
if (v.id == uniqueID) then
v:SetValue(bEnabled, true)
end
end
end
-- called from Populate and from the ixConfigUnloadedList net message
function PANEL:UpdateUnloaded(bNoSizeToContents)
for _, v in pairs(self:GetRows()) do
if (ix.plugin.unloaded[v.id]) then
v:SetValue(false, true)
end
end
for k, v in SortedPairs(ix.plugin.unloaded) do
if (ix.plugin.list[k]) then
-- if this plugin is in the loaded plugins list then it's queued for an unload - don't display it in this category
continue
end
local row = self:AddRow(ix.type.bool, self.unloadedCategory)
row.id = k
row.setting:SetEnabledText(L("on"):utf8upper())
row.setting:SetDisabledText(L("off"):utf8upper())
row.setting:SizeToContents()
row:SetText(k)
row:SetValue(!v, true)
row.OnValueChanged = function(panel, bEnabled)
self:OnPluginToggled(k, bEnabled)
end
end
if (!bNoSizeToContents) then
self:SizeToContents()
end
end
vgui.Register("ixPluginManager", PANEL, "ixSettings")
================================================
FILE: gamemode/core/derma/cl_credits.lua
================================================
local CREDITS = {
{"Alex Grist", "76561197979205163", {"creditLeadDeveloper", "creditManager"}},
{"Igor Radovanovic", "76561197990111113", {"creditLeadDeveloper", "creditUIDesigner"}},
{"Jaydawg", "76561197970371430", {"creditTester"}}
}
local SPECIALS = {
{
{"Luna", "76561197988658543"},
{"Rain GBizzle", "76561198036111376"}
},
{
{"Black Tea", "76561197999893894"}
}
}
local MISC = {
{"nebulous", "Staff members finding bugs and providing input"},
{"Contributors", "Ongoing support from various developers via GitHub"},
{"NutScript", "Providing the base framework to build upon"}
}
local url = "https://gethelix.co/"
local padding = 32
-- logo
local PANEL = {}
function PANEL:Init()
self:SetTall(ScrH() * 0.60)
self:Dock(TOP)
end
function PANEL:Paint(width, height)
derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25)
-- title
surface.SetFont("ixIntroSubtitleFont")
local text = L("helix"):lower()
local textWidth, textHeight = surface.GetTextSize(text)
surface.SetTextColor(color_white)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5)
surface.DrawText(text)
-- version
surface.SetFont("ixMenuMiniFont")
surface.SetTextColor(200, 200, 200, 255)
surface.SetTextPos(width * 0.5 + textWidth * 0.5, height * 0.5 - textHeight * 0.5)
surface.DrawText(GAMEMODE.Version)
end
vgui.Register("ixCreditsLogo", PANEL, "Panel")
-- nametag
PANEL = {}
function PANEL:Init()
self.name = self:Add("DLabel")
self.name:SetFont("ixMenuButtonFontThick")
self.avatar = self:Add("AvatarImage")
end
function PANEL:SetName(name)
self.name:SetText(name)
end
function PANEL:SetAvatar(steamid)
self.avatar:SetSteamID(steamid, 64)
end
function PANEL:PerformLayout(width, height)
self.name:SetPos(width - self.name:GetWide(), 0)
self.avatar:MoveLeftOf(self.name, padding * 0.5)
end
function PANEL:SizeToContents()
self.name:SizeToContents()
local tall = self.name:GetTall()
self.avatar:SetSize(tall, tall)
self:SetSize(self.name:GetWide() + self.avatar:GetWide() + padding * 0.5, self.name:GetTall())
end
vgui.Register("ixCreditsNametag", PANEL, "Panel")
-- name row
PANEL = {}
function PANEL:Init()
self:DockMargin(0, padding, 0, 0)
self:Dock(TOP)
self.nametag = self:Add("ixCreditsNametag")
self.tags = self:Add("DLabel")
self.tags:SetFont("ixMenuButtonFont")
self:SizeToContents()
end
function PANEL:SetName(name)
self.nametag:SetName(name)
end
function PANEL:SetAvatar(steamid)
self.nametag:SetAvatar(steamid)
end
function PANEL:SetTags(tags)
for i = 1, #tags do
tags[i] = L(tags[i])
end
self.tags:SetText(table.concat(tags, "\n"))
end
function PANEL:Paint(width, height)
surface.SetDrawColor(ix.config.Get("color"))
surface.DrawRect(width * 0.5 - 1, 0, 1, height)
end
function PANEL:PerformLayout(width, height)
self.nametag:SetPos(width * 0.5 - self.nametag:GetWide() - padding, 0)
self.tags:SetPos(width * 0.5 + padding, 0)
end
function PANEL:SizeToContents()
self.nametag:SizeToContents()
self.tags:SizeToContents()
self:SetTall(math.max(self.nametag:GetTall(), self.tags:GetTall()))
end
vgui.Register("ixCreditsRow", PANEL, "Panel")
PANEL = {}
function PANEL:Init()
self.left = {}
self.right = {}
end
function PANEL:AddLeft(name, steamid)
local nametag = self:Add("ixCreditsNametag")
nametag:SetName(name)
nametag:SetAvatar(steamid)
nametag:SizeToContents()
self.left[#self.left + 1] = nametag
end
function PANEL:AddRight(name, steamid)
local nametag = self:Add("ixCreditsNametag")
nametag:SetName(name)
nametag:SetAvatar(steamid)
nametag:SizeToContents()
self.right[#self.right + 1] = nametag
end
function PANEL:PerformLayout(width, height)
local y = 0
for _, v in ipairs(self.left) do
v:SetPos(width * 0.25 - v:GetWide() * 0.5, y)
y = y + v:GetTall() + padding
end
y = 0
for _, v in ipairs(self.right) do
v:SetPos(width * 0.75 - v:GetWide() * 0.5, y)
y = y + v:GetTall() + padding
end
if (IsValid(self.center)) then
self.center:SetPos(width * 0.5 - self.center:GetWide() * 0.5, y)
end
end
function PANEL:SizeToContents()
local heightLeft, heightRight, centerHeight = 0, 0, 0
if (#self.left > #self.right) then
local center = self.left[#self.left]
centerHeight = center:GetTall()
self.center = center
self.left[#self.left] = nil
elseif (#self.right > #self.left) then
local center = self.right[#self.right]
centerHeight = center:GetTall()
self.center = center
self.right[#self.right] = nil
end
for _, v in ipairs(self.left) do
heightLeft = heightLeft + v:GetTall() + padding
end
for _, v in ipairs(self.right) do
heightRight = heightRight + v:GetTall() + padding
end
self:SetTall(math.max(heightLeft, heightRight) + centerHeight)
end
vgui.Register("ixCreditsSpecials", PANEL, "Panel")
PANEL = {}
function PANEL:Init()
self:Add("ixCreditsLogo")
local link = self:Add("DLabel", self)
link:SetFont("ixMenuMiniFont")
link:SetTextColor(Color(200, 200, 200, 255))
link:SetText(url)
link:SetContentAlignment(5)
link:Dock(TOP)
link:SizeToContents()
link:SetMouseInputEnabled(true)
link:SetCursor("hand")
link.OnMousePressed = function()
gui.OpenURL(url)
end
for _, v in ipairs(CREDITS) do
local row = self:Add("ixCreditsRow")
row:SetName(v[1])
row:SetAvatar(v[2])
row:SetTags(v[3])
row:SizeToContents()
end
local specials = self:Add("ixLabel")
specials:SetFont("ixMenuButtonFont")
specials:SetText(L("creditSpecial"):utf8upper())
specials:SetTextColor(ix.config.Get("color"))
specials:SetDropShadow(1)
specials:SetKerning(16)
specials:SetContentAlignment(5)
specials:DockMargin(0, padding * 2, 0, padding)
specials:Dock(TOP)
specials:SizeToContents()
local specialList = self:Add("ixCreditsSpecials")
specialList:DockMargin(0, padding, 0, 0)
specialList:Dock(TOP)
for _, v in ipairs(SPECIALS[1]) do
specialList:AddLeft(v[1], v[2])
end
for _, v in ipairs(SPECIALS[2]) do
specialList:AddRight(v[1], v[2])
end
specialList:SizeToContents()
-- less more padding if there's a center column nametag
if (IsValid(specialList.center)) then
specialList:DockMargin(0, padding, 0, padding)
end
for _, v in ipairs(MISC) do
local title = self:Add("DLabel")
title:SetFont("ixMenuButtonFontThick")
title:SetText(v[1])
title:SetContentAlignment(5)
title:SizeToContents()
title:DockMargin(0, padding, 0, 0)
title:Dock(TOP)
local description = self:Add("DLabel")
description:SetFont("ixSmallTitleFont")
description:SetText(v[2])
description:SetContentAlignment(5)
description:SizeToContents()
description:Dock(TOP)
end
self:Dock(TOP)
self:SizeToContents()
end
function PANEL:SizeToContents()
local height = padding
for _, v in pairs(self:GetChildren()) do
local _, top, _, bottom = v:GetDockMargin()
height = height + v:GetTall() + top + bottom
end
self:SetTall(height)
end
vgui.Register("ixCredits", PANEL, "Panel")
hook.Add("PopulateHelpMenu", "ixCredits", function(tabs)
tabs["credits"] = function(container)
container:Add("ixCredits")
end
end)
================================================
FILE: gamemode/core/derma/cl_deathscreen.lua
================================================
local PANEL = {}
function PANEL:Init()
local scrW, scrH = ScrW(), ScrH()
self:SetSize(scrW, scrH)
self:SetPos(0, 0)
local text = string.utf8upper(L("youreDead"))
surface.SetFont("ixMenuButtonHugeFont")
local textW, textH = surface.GetTextSize(text)
self.label = self:Add("DLabel")
self.label:SetPaintedManually(true)
self.label:SetPos(scrW * 0.5 - textW * 0.5, scrH * 0.5 - textH * 0.5)
self.label:SetFont("ixMenuButtonHugeFont")
self.label:SetText(text)
self.label:SizeToContents()
self.progress = 0
self:CreateAnimation(ix.config.Get("spawnTime", 5), {
bIgnoreConfig = true,
target = {progress = 1},
OnComplete = function(animation, panel)
if (!panel:IsClosing()) then
panel:Close()
end
end
})
end
function PANEL:Think()
self.label:SetAlpha(((self.progress - 0.3) / 0.3) * 255)
end
function PANEL:IsClosing()
return self.bIsClosing
end
function PANEL:Close()
self.bIsClosing = true
self:CreateAnimation(2, {
index = 2,
bIgnoreConfig = true,
target = {progress = 0},
OnComplete = function(animation, panel)
panel:Remove()
end
})
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintDeathScreenBackground", self, width, height, self.progress)
self.label:PaintManual()
derma.SkinFunc("PaintDeathScreen", self, width, height, self.progress)
end
vgui.Register("ixDeathScreen", PANEL, "Panel")
================================================
FILE: gamemode/core/derma/cl_dev_icon.lua
================================================
--Icon Editor Base and Math Scale Functions from: https://github.com/TeslaCloud/flux-ce/tree/master
local scaleFactorX = 1 / 1920
local scaleFactorY = 1 / 1080
local function scale(size)
return math.floor(size * (ScrH() * scaleFactorY))
end
local function scale_x(size)
return math.floor(size * (ScrW() * scaleFactorX))
end
local PANEL = {}
function PANEL:Init()
if (IsValid(ix.gui.dev_icon)) then
ix.gui.dev_icon:Remove()
end
ix.gui.dev_icon = self
local pW, pH = ScrW() * 0.6, ScrH() * 0.6
self:SetSize(pW, pH)
self:MakePopup()
self:Center()
local buttonSize = scale(48)
self.model = vgui.Create("DAdjustableModelPanel", self)
self.model:SetSize(pW * 0.5, pH - buttonSize)
self.model:Dock(LEFT)
self.model:DockMargin(0, 0, 0, buttonSize + scale(8))
self.model:SetModel("models/props_borealis/bluebarrel001.mdl")
self.model:SetLookAt(Vector(0, 0, 0))
self.model.LayoutEntity = function() end
local x = scale_x(4)
self.best = vgui.Create("DButton", self)
self.best:SetSize(buttonSize, buttonSize)
self.best:SetPos(x, pH - buttonSize - scale(4))
self.best:SetFont("ixIconsMenuButton")
self.best:SetText("b")
self.best:SetTooltip(L("iconEditorAlignBest"))
self.best.DoClick = function()
local entity = self.model:GetEntity()
local pos = entity:GetPos()
local camData = PositionSpawnIcon(entity, pos)
if (camData) then
self.model:SetCamPos(camData.origin)
self.model:SetFOV(camData.fov)
self.model:SetLookAng(camData.angles)
end
end
x = x + buttonSize + scale_x(4)
self.front = vgui.Create("DButton", self)
self.front:SetSize(buttonSize, buttonSize)
self.front:SetPos(x, pH - buttonSize - scale(4))
self.front:SetFont("ixIconsMenuButton")
self.front:SetText("m")
self.front:SetTooltip(L("iconEditorAlignFront"))
self.front.DoClick = function()
local entity = self.model:GetEntity()
local pos = entity:GetPos()
local camPos = pos + Vector(-200, 0, 0)
self.model:SetCamPos(camPos)
self.model:SetFOV(45)
self.model:SetLookAng((camPos * -1):Angle())
end
x = x + buttonSize + scale_x(4)
self.above = vgui.Create("DButton", self)
self.above:SetSize(buttonSize, buttonSize)
self.above:SetPos(x, pH - buttonSize - scale(4))
self.above:SetFont("ixIconsMenuButton")
self.above:SetText("u")
self.above:SetTooltip(L("iconEditorAlignAbove"))
self.above.DoClick = function()
local entity = self.model:GetEntity()
local pos = entity:GetPos()
local camPos = pos + Vector(0, 0, 200)
self.model:SetCamPos(camPos)
self.model:SetFOV(45)
self.model:SetLookAng((camPos * -1):Angle())
end
x = x + buttonSize + scale_x(4)
self.right = vgui.Create("DButton", self)
self.right:SetSize(buttonSize, buttonSize)
self.right:SetPos(x, pH - buttonSize - scale(4))
self.right:SetFont("ixIconsMenuButton")
self.right:SetText("t")
self.right:SetTooltip(L("iconEditorAlignRight"))
self.right.DoClick = function()
local entity = self.model:GetEntity()
local pos = entity:GetPos()
local camPos = pos + Vector(0, 200, 0)
self.model:SetCamPos(camPos)
self.model:SetFOV(45)
self.model:SetLookAng((camPos * -1):Angle())
end
x = x + buttonSize + scale_x(4)
self.center = vgui.Create("DButton", self)
self.center:SetSize(buttonSize, buttonSize)
self.center:SetPos(x, pH - buttonSize - scale(4))
self.center:SetFont("ixIconsMenuButton")
self.center:SetText("T")
self.center:SetTooltip(L("iconEditorAlignCenter"))
self.center.DoClick = function()
local entity = self.model:GetEntity()
local pos = entity:GetPos()
self.model:SetCamPos(pos)
self.model:SetFOV(45)
self.model:SetLookAng(Angle(0, -180, 0))
end
self.best:DoClick()
self.preview = vgui.Create("DPanel", self)
self.preview:Dock(FILL)
self.preview:DockMargin(scale_x(4), 0, 0, 0)
self.preview:DockPadding(scale_x(4), scale(4), scale_x(4), scale(4))
self.preview.Paint = function(pnl, w, h)
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100))
end
self.modelLabel = self.preview:Add("DLabel")
self.modelLabel:Dock(TOP)
self.modelLabel:SetText("Model")
self.modelLabel:SetFont("ixMenuButtonFontSmall")
self.modelLabel:DockMargin(4, 4, 4, 4)
self.modelPath = vgui.Create("ixTextEntry", self.preview)
self.modelPath:SetValue(self.model:GetModel())
self.modelPath:Dock(TOP)
self.modelPath:SetFont("ixMenuButtonFontSmall")
self.modelPath:SetPlaceholderText("Model...")
self.modelPath.OnEnter = function(pnl)
local model = pnl:GetValue()
if (model and model != "") then
self.model:SetModel(model)
self.item:Rebuild()
end
end
self.modelPath.OnLoseFocus = function(pnl)
local model = pnl:GetValue()
if (model and model != "") then
self.model:SetModel(model)
self.item:Rebuild()
end
end
self.width = vgui.Create("ixSettingsRowNumber", self.preview)
self.width:Dock(TOP)
self.width:SetText(L("iconEditorWidth"))
self.width:SetMin(1)
self.width:SetMax(24)
self.width:SetDecimals(0)
self.width:SetValue(1)
self.width.OnValueChanged = function(pnl, value)
self.item:Rebuild()
end
self.height = vgui.Create("ixSettingsRowNumber", self.preview)
self.height:Dock(TOP)
self.height:SetText(L("iconEditorHeight"))
self.height:SetMin(1)
self.height:SetMax(24)
self.height:SetDecimals(0)
self.height:SetValue(1)
self.height.OnValueChanged = function(pnl, value)
self.item:Rebuild()
end
self.itemPanel = vgui.Create("DPanel", self.preview)
self.itemPanel:Dock(FILL)
self.itemPanel.Paint = function(pnl, w, h)
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100))
end
self.item = vgui.Create("DModelPanel", self.itemPanel)
self.item:SetMouseInputEnabled(false)
self.item.LayoutEntity = function() end
self.item.PaintOver = function(pnl, w, h)
surface.SetDrawColor(color_white)
surface.DrawOutlinedRect(0, 0, w, h)
end
self.item.Rebuild = function(pnl)
local slotSize = 64
local padding = scale(2)
local slotWidth, slotHeight = math.Round(self.width:GetValue()), math.Round(self.height:GetValue())
local w, h = slotWidth * (slotSize + padding) - padding, slotHeight * (slotSize + padding) - padding
pnl:SetModel(self.model:GetModel())
pnl:SetCamPos(self.model:GetCamPos())
pnl:SetFOV(self.model:GetFOV())
pnl:SetLookAng(self.model:GetLookAng())
pnl:SetSize(w, h)
pnl:Center()
end
self.item:Rebuild()
timer.Create("ix_icon_editor_update", 0.5, 0, function()
if IsValid(self) and IsValid(self.model) then
self.item:Rebuild()
else
timer.Remove("ix_icon_editor_update")
end
end)
self.copy = vgui.Create("DButton", self)
self.copy:SetSize(buttonSize, buttonSize)
self.copy:SetPos(pW - buttonSize - scale_x(12), pH - buttonSize - scale(12))
self.copy:SetFont("ixIconsMenuButton")
self.copy:SetText("}")
self.copy:SetTooltip(L("iconEditorCopy"))
self.copy.DoClick = function()
local camPos = self.model:GetCamPos()
local camAng = self.model:GetLookAng()
local str = "ITEM.model = \""..self.model:GetModel().."\"\n"
.."ITEM.width = "..math.Round(self.width:GetValue()).."\n"
.."ITEM.height = "..math.Round(self.height:GetValue()).."\n"
.."ITEM.iconCam = {\n"
.."\tpos = Vector("..math.Round(camPos.x, 2)..", "..math.Round(camPos.y, 2)..", "..math.Round(camPos.z, 2).."),\n"
.."\tang = Angle("..math.Round(camAng.p, 2)..", "..math.Round(camAng.y, 2)..", "..math.Round(camAng.r, 2).."),\n"
.."\tfov = "..math.Round(self.model:GetFOV(), 2).."\n"
.."}\n"
SetClipboardText(str)
ix.util.Notify(L("iconEditorCopied"))
end
end
vgui.Register("ix_icon_editor", PANEL, "DFrame")
concommand.Add("ix_dev_icon", function()
if (LocalPlayer():IsAdmin()) then
vgui.Create("ix_icon_editor")
end
end)
================================================
FILE: gamemode/core/derma/cl_entitymenu.lua
================================================
local animationTime = 1
local padding = 32
-- entity menu button
DEFINE_BASECLASS("ixMenuButton")
local PANEL = {}
AccessorFunc(PANEL, "callback", "Callback")
function PANEL:Init()
self:SetTall(ScrH() * 0.1)
self:Dock(TOP)
end
function PANEL:DoClick()
local bStatus = true
local parent = ix.menu.panel
local entity = parent:GetEntity()
if (parent.bClosing) then
return
end
if (isfunction(self.callback)) then
bStatus = self.callback()
end
if (bStatus != false) then
ix.menu.NetworkChoice(entity, self.originalText, bStatus)
end
parent:Remove()
end
function PANEL:SetText(text)
self.originalText = text
BaseClass.SetText(self, text)
end
vgui.Register("ixEntityMenuButton", PANEL, "ixMenuButton")
-- entity menu list
DEFINE_BASECLASS("EditablePanel")
PANEL = {}
function PANEL:Init()
self.list = {}
end
function PANEL:AddOption(text, callback)
local panel = self:Add("ixEntityMenuButton")
panel:SetText(text)
panel:SetCallback(callback)
panel:Dock(TOP)
if (self.bPaintedManually) then
panel:SetPaintedManually(true)
end
self.list[#self.list + 1] = panel
end
function PANEL:SizeToContents()
local height = 0
for i = 1, #self.list do
height = height + self.list[i]:GetTall()
end
self:SetSize(ScrW() * 0.5 - padding * 2, height)
end
function PANEL:Center()
local parent = self:GetParent()
self:SetPos(
ScrW() * 0.5 + padding,
parent:GetTall() * 0.5 - self:GetTall() * 0.5
)
end
function PANEL:SetPaintedManually(bValue)
if (bValue) then
for i = 1, #self.list do
self.list[i]:SetPaintedManually(true)
end
self.bPaintedManually = true
end
BaseClass.SetPaintedManually(self, bValue)
end
function PANEL:PaintManual()
BaseClass.PaintManual(self)
local list = self.list
for i = 1, #list do
list[i]:PaintManual()
end
end
vgui.Register("ixEntityMenuList", PANEL, "EditablePanel")
-- entity menu
DEFINE_BASECLASS("EditablePanel")
PANEL = {}
AccessorFunc(PANEL, "entity", "Entity")
AccessorFunc(PANEL, "bClosing", "IsClosing", FORCE_BOOL)
AccessorFunc(PANEL, "desiredHeight", "DesiredHeight", FORCE_NUMBER)
function PANEL:Init()
if (IsValid(ix.menu.panel)) then
self:Remove()
return
end
-- close entity tooltip if it's open
if (IsValid(ix.gui.entityInfo)) then
ix.gui.entityInfo:Remove()
end
ix.menu.panel = self
self:SetSize(ScrW(), ScrH())
self:SetPos(0, 0)
self.list = self:Add("ixEntityMenuList")
self.list:SetPaintedManually(true)
self.desiredHeight = 0
self.blur = 0
self.alpha = 1
self.bClosing = false
self.lastPosition = vector_origin
self:CreateAnimation(animationTime, {
target = {blur = 1},
easing = "outQuint"
})
self:MakePopup()
end
function PANEL:SetOptions(options)
for k, v in pairs(options) do
self.list:AddOption(k, v)
end
self.list:SizeToContents()
self.list:Center()
end
function PANEL:GetApproximateScreenHeight(entity, distanceSqr)
return IsValid(entity) and
(entity:BoundingRadius() * (entity:IsPlayer() and 1.5 or 1) or 0) / math.sqrt(distanceSqr) * self:GetTall() or 0
end
function PANEL:Think()
local entity = self.entity
local distance = 0
if (IsValid(entity)) then
local position = entity:GetPos()
distance = LocalPlayer():GetShootPos():DistToSqr(position)
if (distance > 65536) then
self:Remove()
return
end
self.lastPosition = position
end
self.desiredHeight = math.max(self.list:GetTall() + padding * 2, self:GetApproximateScreenHeight(entity, distance))
end
function PANEL:Paint(width, height) -- luacheck: ignore 312
local selfHalf = self:GetTall() * 0.5
local entity = self.entity
height = self.desiredHeight + padding * 2
width = self.blur * width
local y = selfHalf - height * 0.5
DisableClipping(true) -- for cheap blur
render.SetScissorRect(0, y, width, y + height, true)
if (IsValid(entity)) then
cam.Start3D()
ix.util.ResetStencilValues()
render.SetStencilEnable(true)
cam.IgnoreZ(true)
render.SetStencilWriteMask(29)
render.SetStencilTestMask(29)
render.SetStencilReferenceValue(29)
render.SetStencilCompareFunction(STENCIL_ALWAYS)
render.SetStencilPassOperation(STENCIL_REPLACE)
render.SetStencilFailOperation(STENCIL_KEEP)
render.SetStencilZFailOperation(STENCIL_KEEP)
entity:DrawModel()
render.SetStencilCompareFunction(STENCIL_NOTEQUAL)
render.SetStencilPassOperation(STENCIL_KEEP)
cam.Start2D()
ix.util.DrawBlur(self, 10)
cam.End2D()
cam.IgnoreZ(false)
render.SetStencilEnable(false)
cam.End3D()
else
ix.util.DrawBlur(self, 10)
end
render.SetScissorRect(0, 0, 0, 0, false)
DisableClipping(false)
-- scissor again because 3d rendering messes with the clipping apparently?
render.SetScissorRect(0, y, width, y + height, true)
surface.SetDrawColor(ix.config.Get("color"))
surface.DrawRect(ScrW() * 0.5, y + padding, 1, height - padding * 2)
self.list:PaintManual()
render.SetScissorRect(0, 0, 0, 0, false)
end
function PANEL:GetOverviewInfo(origin, angles)
local entity = self.entity
if (IsValid(entity)) then
local radius = entity:BoundingRadius() * (entity:IsPlayer() and 0.5 or 1)
local center = entity:LocalToWorld(entity:OBBCenter()) + LocalPlayer():GetRight() * radius
return LerpAngle(self.bClosing and self.alpha or self.blur, angles, (center - origin):Angle())
end
return angles
end
function PANEL:OnMousePressed(code)
if (code == MOUSE_LEFT) then
self:Remove()
end
end
function PANEL:Remove()
if (self.bClosing) then
return
end
self.bClosing = true
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
gui.EnableScreenClicker(false)
self:CreateAnimation(animationTime * 0.5, {
target = {alpha = 0},
index = 2,
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(panel.alpha * 255)
end,
OnComplete = function(animation, panel)
ix.menu.panel = nil
BaseClass.Remove(self)
end
})
end
vgui.Register("ixEntityMenu", PANEL, "EditablePanel")
================================================
FILE: gamemode/core/derma/cl_generic.lua
================================================
-- generic panels that are applicable anywhere
-- used for prominent text entries
DEFINE_BASECLASS("DTextEntry")
local PANEL = {}
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
function PANEL:Init()
self:SetPaintBackground(false)
self:SetTextColor(color_white)
self.backgroundColor = Color(255, 255, 255, 25)
end
function PANEL:SetFont(font)
surface.SetFont(font)
local _, height = surface.GetTextSize("W@")
self:SetTall(height)
BaseClass.SetFont(self, font)
end
function PANEL:Paint(width, height)
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor)
BaseClass.Paint(self, width, height)
end
vgui.Register("ixTextEntry", PANEL, "DTextEntry")
-- similar to a frame, but is mainly used for grouping panels together in a list
PANEL = {}
AccessorFunc(PANEL, "text", "Text", FORCE_STRING)
AccessorFunc(PANEL, "color", "Color")
function PANEL:Init()
self.text = ""
self.paddingTop = 32
local skin = self:GetSkin()
if (skin and skin.fontCategoryBlur) then
surface.SetFont(skin.fontCategoryBlur)
self.paddingTop = select(2, surface.GetTextSize("W@")) + 6
end
self:DockPadding(1, self.paddingTop, 1, 1)
end
function PANEL:SizeToContents()
local height = self.paddingTop + 1
for _, v in ipairs(self:GetChildren()) do
if (IsValid(v) and v:IsVisible()) then
local _, top, _, bottom = v:GetDockMargin()
height = height + v:GetTall() + top + bottom
end
end
self:SetTall(height)
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintCategoryPanel", self, self.text, self.color)
end
vgui.Register("ixCategoryPanel", PANEL, "EditablePanel")
-- segmented progress bar
PANEL = {}
AccessorFunc(PANEL, "font", "Font", FORCE_STRING)
AccessorFunc(PANEL, "barColor", "BarColor")
AccessorFunc(PANEL, "textColor", "TextColor")
AccessorFunc(PANEL, "progress", "Progress", FORCE_NUMBER)
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
AccessorFunc(PANEL, "easingType", "EasingType", FORCE_STRING)
function PANEL:Init()
self.segments = {}
self.padding = ScrH() * 0.01
self.fraction = 0
self.animationTime = 0.5
self.easingType = "outQuint"
self.progress = 0
end
function PANEL:AddSegment(text)
local id = #self.segments + 1
if (text:sub(1, 1) == "@") then
text = L(text:sub(2))
end
self.segments[id] = text
return id
end
function PANEL:AddSegments(...)
local segments = {...}
for i = 1, #segments do
self:AddSegment(segments[i])
end
end
function PANEL:GetSegments()
return self.segments
end
function PANEL:SetProgress(segment)
self.progress = math.Clamp(segment, 0, #self.segments)
self:CreateAnimation(self.animationTime, {
target = {fraction = self.progress / #self.segments},
easing = self.easingType
})
end
function PANEL:IncrementProgress(amount)
self:SetProgress(self.progress + (amount or 1))
end
function PANEL:DecrementProgress(amount)
self:SetProgress(self.progress - (amount or 1))
end
function PANEL:GetFraction()
return self.fraction
end
function PANEL:SizeToContents()
self:SetTall(draw.GetFontHeight(self.font or self:GetSkin().fontSegmentedProgress) + self.padding)
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintSegmentedProgressBackground", self, width, height)
if (#self.segments > 0) then
derma.SkinFunc("PaintSegmentedProgress", self, width, height)
end
end
vgui.Register("ixSegmentedProgress", PANEL, "Panel")
-- list of labelled information
PANEL = {}
AccessorFunc(PANEL, "labelColor", "LabelColor")
AccessorFunc(PANEL, "textColor", "TextColor")
AccessorFunc(PANEL, "list", "List")
AccessorFunc(PANEL, "minWidth", "MinimumWidth", FORCE_NUMBER)
function PANEL:Init()
self.label = self:Add("DLabel")
self.label:SetFont("ixMediumFont")
self.label:SetExpensiveShadow(1)
self.label:SetTextColor(color_white)
self.label:SetText("Label")
self.label:SetContentAlignment(5)
self.label:Dock(LEFT)
self.label:DockMargin(0, 0, 4, 0)
self.label:SizeToContents()
self.label.Paint = function(this, width, height)
derma.SkinFunc("PaintListRow", this, width, height)
end
self.text = self:Add("DLabel")
self.text:SetFont("ixMediumLightFont")
self.text:SetTextColor(color_white)
self.text:SetText("Text")
self.text:SetTextInset(8, 0)
self.text:Dock(FILL)
self.text:DockMargin(4, 0, 0, 0)
self.text:SizeToContents()
self.text.Paint = function(this, width, height)
derma.SkinFunc("PaintListRow", this, width, height)
end
self:DockMargin(0, 0, 0, 8)
self.list = {}
self.minWidth = 100
end
function PANEL:SetRightPanel(panel)
self.text:Remove()
self.text = self:Add(panel)
self.text:Dock(FILL)
self.text:DockMargin(8, 4, 4, 4)
self.text:SizeToContents()
end
function PANEL:SetList(list, bNoAdd)
if (!bNoAdd) then
list[#list + 1] = self
end
self.list = list
end
function PANEL:UpdateLabelWidths()
local maxWidth = self.label:GetWide()
for i = 1, #self.list do
maxWidth = math.max(self.list[i]:GetLabelWidth(), maxWidth)
end
maxWidth = math.max(self.minWidth, maxWidth)
for i = 1, #self.list do
self.list[i]:SetLabelWidth(maxWidth)
end
end
function PANEL:SetLabelColor(color)
self.label:SetTextColor(color)
end
function PANEL:SetTextColor(color)
self.text:SetTextColor(color)
end
function PANEL:SetLabelText(text)
self.label:SetText(text)
self.label:SizeToContents()
self:UpdateLabelWidths()
end
function PANEL:SetText(text)
self.text:SetText(text)
self.text:SizeToContents()
end
function PANEL:SetLabelWidth(width)
self.label:SetWide(width)
end
function PANEL:GetLabelWidth(bWithoutMargin)
if (!bWithoutMargin) then
return self.label:GetWide()
end
local left, _, right, _ = self.label:GetDockMargin()
return self.label:GetWide() + left + right
end
function PANEL:SizeToContents()
self:SetTall(math.max(self.label:GetTall(), self.text:GetTall()) + 8)
end
vgui.Register("ixListRow", PANEL, "Panel")
-- alternative checkbox
DEFINE_BASECLASS("EditablePanel")
PANEL = {}
AccessorFunc(PANEL, "enabledText", "EnabledText", FORCE_STRING)
AccessorFunc(PANEL, "disabledText", "DisabledText", FORCE_STRING)
AccessorFunc(PANEL, "font", "Font", FORCE_STRING)
AccessorFunc(PANEL, "bChecked", "Checked", FORCE_BOOL)
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER)
PANEL.GetValue = PANEL.GetChecked
function PANEL:Init()
self:SetMouseInputEnabled(true)
self:SetCursor("hand")
self.enabledText = L("yes"):utf8upper()
self.disabledText = L("no"):utf8upper()
self.font = "ixMenuButtonFont"
self.animationTime = 0.5
self.bChecked = false
self.labelPadding = 8
self.animationOffset = 0
self:SizeToContents()
end
function PANEL:SizeToContents()
BaseClass.SizeToContents(self)
surface.SetFont(self.font)
self:SetWide(math.max(surface.GetTextSize(self.enabledText), surface.GetTextSize(self.disabledText)) + self.labelPadding)
end
-- can be overidden to change audio params
function PANEL:GetAudioFeedback()
return "weapons/ar2/ar2_empty.wav", 75, self.bChecked and 150 or 125, 0.25
end
function PANEL:EmitFeedback()
LocalPlayer():EmitSound(self:GetAudioFeedback())
end
function PANEL:SetChecked(bChecked, bInstant)
self.bChecked = tobool(bChecked)
self:CreateAnimation(bInstant and 0 or self.animationTime, {
index = 1,
target = {
animationOffset = bChecked and 1 or 0
},
easing = "outElastic"
})
if (!bInstant) then
self:EmitFeedback()
end
end
function PANEL:OnMousePressed(code)
if (code == MOUSE_LEFT) then
self:SetChecked(!self.bChecked)
self:DoClick()
end
end
function PANEL:DoClick()
end
function PANEL:Paint(width, height)
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
surface.DrawRect(0, 0, width, height)
local offset = self.animationOffset
surface.SetFont(self.font)
local text = self.disabledText
local textWidth, textHeight = surface.GetTextSize(text)
local y = offset * -textHeight
surface.SetTextColor(250, 60, 60, 255)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
surface.DrawText(text)
text = self.enabledText
y = y + textHeight
textWidth, textHeight = surface.GetTextSize(text)
surface.SetTextColor(30, 250, 30, 255)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
surface.DrawText(text)
end
vgui.Register("ixCheckBox", PANEL, "EditablePanel")
-- alternative num slider
PANEL = {}
AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER)
function PANEL:Init()
self.labelPadding = 8
surface.SetFont("ixMenuButtonFont")
local totalWidth = surface.GetTextSize("999") -- start off with 3 digit width
self.label = self:Add("DLabel")
self.label:Dock(RIGHT)
self.label:SetWide(totalWidth + self.labelPadding)
self.label:SetContentAlignment(5)
self.label:SetFont("ixMenuButtonFont")
self.label.Paint = function(panel, width, height)
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
surface.DrawRect(0, 0, width, height)
end
self.label.SizeToContents = function(panel)
surface.SetFont(panel:GetFont())
local textWidth = surface.GetTextSize(panel:GetText())
if (textWidth > totalWidth) then
panel:SetWide(textWidth + self.labelPadding)
elseif (panel:GetWide() > totalWidth + self.labelPadding) then
panel:SetWide(totalWidth + self.labelPadding)
end
end
self.slider = self:Add("ixSlider")
self.slider:Dock(FILL)
self.slider:DockMargin(0, 0, 4, 0)
self.slider.OnValueChanged = function(panel)
self:OnValueChanged()
end
self.slider.OnValueUpdated = function(panel)
self.label:SetText(string.format("%0." .. tostring(panel:GetDecimals()) .. "f", tostring(panel:GetValue())))
self.label:SizeToContents()
self:OnValueUpdated()
end
end
function PANEL:GetLabel()
return self.label
end
function PANEL:GetSlider()
return self.slider
end
function PANEL:SetValue(value, bNoNotify)
value = tonumber(value) or self.slider:GetMin()
self.slider:SetValue(value, bNoNotify)
self.label:SetText(string.format("%0." .. tostring(self:GetDecimals()) .. "f", tostring(self.slider:GetValue())))
self.label:SizeToContents()
end
function PANEL:GetValue()
return self.slider:GetValue()
end
function PANEL:GetFraction()
return self.slider:GetFraction()
end
function PANEL:GetVisualFraction()
return self.slider:GetVisualFraction()
end
function PANEL:SetMin(value)
self.slider:SetMin(value)
end
function PANEL:SetMax(value)
self.slider:SetMax(value)
end
function PANEL:GetMin()
return self.slider:GetMin()
end
function PANEL:GetMax()
return self.slider:GetMax()
end
function PANEL:SetDecimals(value)
self.slider:SetDecimals(value)
end
function PANEL:GetDecimals()
return self.slider:GetDecimals()
end
-- called when changed by user
function PANEL:OnValueChanged()
end
-- called when changed while dragging bar
function PANEL:OnValueUpdated()
end
vgui.Register("ixNumSlider", PANEL, "Panel")
-- alternative slider
PANEL = {}
AccessorFunc(PANEL, "bDragging", "Dragging", FORCE_BOOL)
AccessorFunc(PANEL, "min", "Min", FORCE_NUMBER)
AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER)
AccessorFunc(PANEL, "decimals", "Decimals", FORCE_NUMBER)
function PANEL:Init()
self.min = 0
self.max = 10
self.value = 0
self.visualValue = 0
self.decimals = 0
self:SetCursor("hand")
end
function PANEL:SetValue(value, bNoNotify)
self.value = math.Clamp(math.Round(tonumber(value) or self.min, self.decimals), self.min, self.max)
self:ValueUpdated(bNoNotify)
if (!bNoNotify) then
self:OnValueChanged()
end
end
function PANEL:GetValue()
return self.value
end
function PANEL:GetFraction()
return math.Remap(self.value, self.min, self.max, 0, 1)
end
function PANEL:GetVisualFraction()
return math.Remap(self.visualValue, self.min, self.max, 0, 1)
end
function PANEL:OnMousePressed(key)
if (key == MOUSE_LEFT) then
self.bDragging = true
self:MouseCapture(true)
self:OnCursorMoved(self:CursorPos())
end
end
function PANEL:OnMouseReleased(key)
if (self.bDragging) then
self:OnValueChanged()
end
self.bDragging = false
self:MouseCapture(false)
end
function PANEL:OnCursorMoved(x, y)
if (!self.bDragging) then
return
end
x = math.Clamp(x, 0, self:GetWide())
local oldValue = self.value
self.value = math.Clamp(math.Round(
math.Remap(x / self:GetWide(), 0, 1, self.min, self.max), self.decimals
), self.min, self.max)
self:CreateAnimation(0.5, {
index = 1,
target = {visualValue = self.value},
easing = "outQuint"
})
if (self.value != oldValue) then
self:ValueUpdated()
end
end
function PANEL:OnValueChanged()
end
function PANEL:ValueUpdated(bNoNotify)
self:CreateAnimation(bNoNotify and 0 or 0.5, {
index = 1,
target = {visualValue = self.value},
easing = "outQuint"
})
if (!bNoNotify) then
self:OnValueUpdated()
end
end
function PANEL:OnValueUpdated()
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintHelixSlider", self, width, height)
end
vgui.Register("ixSlider", PANEL, "EditablePanel")
--- Alternative to DLabel that adds extra functionality.
-- This panel is meant for drawing single-line text. It can add extra kerning (spaces between letters), and it can forcefully
-- scale the text down to fit the current width, without cutting off any letters. Text scaling is most useful when docking this
-- this panel without knowing what the width could be. For example, text scaling is used for the character name in the character
-- status menu.
-- local label = vgui.Create("ixLabel")
-- label:SetText("hello world")
-- label:SetFont("ixMenuButtonHugeFont")
-- label:SetContentAlignment(5)
-- label:SetTextColor(Color(255, 255, 255, 255))
-- label:SetBackgroundColor(Color(200, 30, 30, 255))
-- label:SetPadding(8)
-- label:SetScaleWidth(true)
-- label:SizeToContents()
-- @panel ixLabel
PANEL = {}
--- Sets the text for this label to display.
-- @realm client
-- @string text Text to display
-- @function SetText
--- Returns the current text for this panel.
-- @realm client
-- @treturn string Current text
-- @function GetText
AccessorFunc(PANEL, "text", "Text", FORCE_STRING)
--- Sets the color of the text to use when drawing.
-- @realm client
-- @color color New color to use
-- @function SetTextColor
--- Returns the current text color for this panel.
-- @realm client
-- @treturn color Current text color
-- @function GetTextColor
AccessorFunc(PANEL, "color", "TextColor")
--- Sets the color of the background to draw behind the text.
-- @realm client
-- @color color New color to use
-- @function SetBackgroundColor
--- Returns the current background color for this panel.
-- @realm client
-- @treturn color Current background color
-- @function GetBackgroundColor
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
--- Sets the spacing between each character of the text in pixels. Set to `0` to disable. Kerning is disabled by default.
-- @realm client
-- @number kerning How far apart to draw each letter
-- @function SetKerning
--- Returns the current kerning for this panel.
-- @realm client
-- @treturn number Current kerning
-- @function GetKerning
AccessorFunc(PANEL, "kerning", "Kerning", FORCE_NUMBER)
--- Sets the font used to draw the text.
-- @realm client
-- @string font Name of the font to use
-- @function SetFont
--- Returns the current font for this panel.
-- @realm client
-- @treturn string Name of current font
-- @function GetFont
AccessorFunc(PANEL, "font", "Font", FORCE_STRING)
--- Changes how the text is aligned when drawing. Valid content alignment values include numbers `1` through `9`. Each number's
-- corresponding alignment is based on its position on a numpad. For example, `1` is bottom-left, `5` is centered, `9` is
-- top-right, etc.
-- @realm client
-- @number alignment Alignment to use
-- @function SetContentAlignment
--- Returns the current content alignment for this panel.
-- @realm client
-- @treturn number Current content alignment
-- @function GetContentAlignment
AccessorFunc(PANEL, "contentAlignment", "ContentAlignment", FORCE_NUMBER)
--- Whether or not to scale the width of the text down to fit the width of this panel, if needed.
-- @realm client
-- @bool bScale Whether or not to scale
-- @function SetScaleWidth
--- Returns whether or not this panel will scale its text down to fit its width.
-- @realm client
-- @treturn bool Whether or not this panel will scale its text
-- @function GetScaleWidth
AccessorFunc(PANEL, "bScaleWidth", "ScaleWidth", FORCE_BOOL)
--- How much spacing to use around the text when its drawn. This uses uniform padding on the top, left, right, and bottom of
-- this panel.
-- @realm client
-- @number padding Padding to use
-- @function SetPadding
--- Returns how much padding this panel has around its text.
-- @realm client
-- @treturn number Current padding
-- @function GetPadding
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
function PANEL:Init()
self.text = ""
self.color = color_white
self.backgroundColor = Color(255, 255, 255, 0)
self.kerning = 0
self.font = "DermaDefault"
self.scaledFont = "DermaDefault"
self.contentAlignment = 5
self.bScaleWidth = false
self.padding = 0
self.shadowDistance = 0
self.bCurrentlyScaling = false
end
function PANEL:SetText(text)
self.text = tostring(text)
end
function PANEL:SetFont(font)
self.font = font
self.scaledFont = font
end
--- Sets the drop shadow to draw behind the text.
-- @realm client
-- @number distance How far away to draw the shadow in pixels. Set to `0` to disable
-- @color[opt] color Color of the shadow. Defaults to a dimmed version of the text color
function PANEL:SetDropShadow(distance, color)
self.shadowDistance = distance or 1
self.shadowColor = color or ix.util.DimColor(self.color, 0.5)
end
PANEL.SetExpensiveShadow = PANEL.SetDropShadow -- aliasing for easier conversion from DLabels
--- Returns the X and Y location of the text taking into account the text alignment and padding.
-- @realm client
-- @internal
-- @number width Width of the panel
-- @number height Height of the panel
-- @number textWidth Width of the text
-- @number textHeight Height of the text
-- @treturn number X location to draw the text
-- @treturn number Y location to draw the text
function PANEL:CalculateAlignment(width, height, textWidth, textHeight)
local alignment = self.contentAlignment
local x, y
if (self.bCurrentlyScaling) then
-- if the text is currently being scaled down, then it's always centered
x = width * 0.5 - textWidth * 0.5
else
-- x alignment
if (alignment == 7 or alignment == 4 or alignment == 1) then
-- left
x = self.padding
elseif (alignment == 8 or alignment == 5 or alignment == 2) then
-- center
x = width * 0.5 - textWidth * 0.5
elseif (alignment == 9 or alignment == 6 or alignment == 3) then
x = width - textWidth - self.padding
end
end
-- y alignment
if (alignment <= 3) then
-- bottom
y = height - textHeight - self.padding
elseif (alignment <= 6) then
-- center
y = height * 0.5 - textHeight * 0.5
else
-- top
y = self.padding
end
return x, y
end
--- Draws the current text with the current kerning.
-- @realm client
-- @internal
-- @number width Width of the panel
-- @number height Height of the panel
function PANEL:DrawKernedText(width, height)
local contentWidth, contentHeight = self:GetContentSize()
local x, y = self:CalculateAlignment(width, height, contentWidth, contentHeight)
for i = 1, self.text:utf8len() do
local character = self.text:utf8sub(i, i)
local textWidth, _ = surface.GetTextSize(character)
local kerning = i == 1 and 0 or self.kerning
local shadowDistance = self.shadowDistance
-- shadow
if (self.shadowDistance > 0) then
surface.SetTextColor(self.shadowColor)
surface.SetTextPos(x + kerning + shadowDistance, y + shadowDistance)
surface.DrawText(character)
end
-- character
surface.SetTextColor(self.color)
surface.SetTextPos(x + kerning, y)
surface.DrawText(character)
x = x + textWidth + kerning
end
end
--- Draws the current text.
-- @realm client
-- @internal
-- @number width Width of the panel
-- @number height Height of the panel
function PANEL:DrawText(width, height)
local textWidth, textHeight = surface.GetTextSize(self.text)
local x, y = self:CalculateAlignment(width, height, textWidth, textHeight)
-- shadow
if (self.shadowDistance > 0) then
surface.SetTextColor(self.shadowColor)
surface.SetTextPos(x + self.shadowDistance, y + self.shadowDistance)
surface.DrawText(self.text)
end
-- text
surface.SetTextColor(self.color)
surface.SetTextPos(x, y)
surface.DrawText(self.text)
end
function PANEL:Paint(width, height)
surface.SetFont(self.font)
surface.SetDrawColor(self.backgroundColor)
surface.DrawRect(0, 0, width, height)
if (self.bScaleWidth) then
local contentWidth, contentHeight = self:GetContentSize()
if (contentWidth > (width - self.padding * 2)) then
local x, y = self:LocalToScreen(self:GetPos())
local scale = width / (contentWidth + self.padding * 2)
local translation = Vector(x + width * 0.5, y - contentHeight * 0.5 + self.padding, 0)
local matrix = Matrix()
matrix:Translate(translation)
matrix:Scale(Vector(scale, scale, 0))
matrix:Translate(-translation)
cam.PushModelMatrix(matrix, true)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
DisableClipping(true)
self.bCurrentlyScaling = true
end
end
if (self.kerning > 0) then
self:DrawKernedText(width, height)
else
self:DrawText(width, height)
end
if (self.bCurrentlyScaling) then
DisableClipping(false)
render.PopFilterMin()
cam.PopModelMatrix()
self.bCurrentlyScaling = false
end
end
--- Returns the size of the text, taking into account the current kerning.
-- @realm client
-- @bool[opt=false] bCalculate Whether or not to recalculate the content size instead of using the cached copy
-- @treturn number Width of the text
-- @treturn number Height of the text
function PANEL:GetContentSize(bCalculate)
if (bCalculate or !self.contentSize) then
surface.SetFont(self.font)
if (self.kerning > 0) then
local width = 0
for i = 1, self.text:utf8len() do
local textWidth, _ = surface.GetTextSize(self.text:utf8sub(i, i))
width = width + textWidth + self.kerning
end
self.contentSize = {width, draw.GetFontHeight(self.font)}
else
self.contentSize = {surface.GetTextSize(self.text)}
end
end
return self.contentSize[1], self.contentSize[2]
end
--- Sets the size of the panel to fit the content size with the current padding. The content size is recalculated when this
-- method is called.
-- @realm client
function PANEL:SizeToContents()
local contentWidth, contentHeight = self:GetContentSize(true)
self:SetSize(contentWidth + self.padding * 2, contentHeight + self.padding * 2)
end
vgui.Register("ixLabel", PANEL, "Panel")
-- text entry with icon
DEFINE_BASECLASS("ixTextEntry")
PANEL = {}
AccessorFunc(PANEL, "icon", "Icon", FORCE_STRING)
AccessorFunc(PANEL, "iconColor", "IconColor")
function PANEL:Init()
self:SetIcon("V")
self:SetFont("ixSmallTitleFont")
self.iconColor = Color(200, 200, 200, 160)
end
function PANEL:SetIcon(newIcon)
surface.SetFont("ixSmallTitleIcons")
self.iconWidth, self.iconHeight = surface.GetTextSize(newIcon)
self.icon = newIcon
self:DockMargin(self.iconWidth + 4, 0, 0, 8)
end
function PANEL:Paint(width, height)
BaseClass.Paint(self, width, height)
-- there's no inset for text entries so we'll have to get creative
DisableClipping(true)
surface.SetDrawColor(self:GetBackgroundColor())
surface.DrawRect(-self.iconWidth - 4, 0, self.iconWidth + 4, height)
surface.SetFont("ixSmallTitleIcons")
surface.SetTextColor(self.iconColor)
surface.SetTextPos(-self.iconWidth - 2, 0)
surface.DrawText(self:GetIcon())
DisableClipping(false)
end
vgui.Register("ixIconTextEntry", PANEL, "ixTextEntry")
================================================
FILE: gamemode/core/derma/cl_help.lua
================================================
local backgroundColor = Color(0, 0, 0, 66)
local PANEL = {}
AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER)
function PANEL:Init()
self:SetWide(180)
self:Dock(LEFT)
self.maxWidth = ScrW() * 0.2
end
function PANEL:Paint(width, height)
surface.SetDrawColor(backgroundColor)
surface.DrawRect(0, 0, width, height)
end
function PANEL:SizeToContents()
local width = 0
for _, v in ipairs(self:GetChildren()) do
width = math.max(width, v:GetWide())
end
self:SetSize(math.max(32, math.min(width, self.maxWidth)), self:GetParent():GetTall())
end
vgui.Register("ixHelpMenuCategories", PANEL, "EditablePanel")
-- help menu
PANEL = {}
function PANEL:Init()
self:Dock(FILL)
self.categories = {}
self.categorySubpanels = {}
self.categoryPanel = self:Add("ixHelpMenuCategories")
self.canvasPanel = self:Add("EditablePanel")
self.canvasPanel:Dock(FILL)
self.idlePanel = self.canvasPanel:Add("Panel")
self.idlePanel:Dock(FILL)
self.idlePanel:DockMargin(8, 0, 0, 0)
self.idlePanel.Paint = function(_, width, height)
surface.SetDrawColor(backgroundColor)
surface.DrawRect(0, 0, width, height)
derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25)
surface.SetFont("ixIntroSubtitleFont")
local text = L("helix"):lower()
local textWidth, textHeight = surface.GetTextSize(text)
surface.SetTextColor(color_white)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.75)
surface.DrawText(text)
surface.SetFont("ixMediumLightFont")
text = L("helpIdle")
local infoWidth, _ = surface.GetTextSize(text)
surface.SetTextColor(color_white)
surface.SetTextPos(width * 0.5 - infoWidth * 0.5, height * 0.5 + textHeight * 0.25)
surface.DrawText(text)
end
local categories = {}
hook.Run("PopulateHelpMenu", categories)
for k, v in SortedPairs(categories) do
if (!isstring(k)) then
ErrorNoHalt("expected string for help menu key\n")
continue
elseif (!isfunction(v)) then
ErrorNoHalt(string.format("expected function for help menu entry '%s'\n", k))
continue
end
self:AddCategory(k)
self.categories[k] = v
end
self.categoryPanel:SizeToContents()
if (ix.gui.lastHelpMenuTab) then
self:OnCategorySelected(ix.gui.lastHelpMenuTab)
end
end
function PANEL:AddCategory(name)
local button = self.categoryPanel:Add("ixMenuButton")
button:SetText(L(name))
button:SizeToContents()
-- @todo don't hardcode this but it's the only panel that needs docking at the bottom so it'll do for now
button:Dock(name == "credits" and BOTTOM or TOP)
button.DoClick = function()
self:OnCategorySelected(name)
end
local panel = self.canvasPanel:Add("DScrollPanel")
panel:SetVisible(false)
panel:Dock(FILL)
panel:DockMargin(8, 0, 0, 0)
panel:GetCanvas():DockPadding(8, 8, 8, 8)
panel.Paint = function(_, width, height)
surface.SetDrawColor(backgroundColor)
surface.DrawRect(0, 0, width, height)
end
-- reverts functionality back to a standard panel in the case that a category will manage its own scrolling
panel.DisableScrolling = function()
panel:GetCanvas():SetVisible(false)
panel:GetVBar():SetVisible(false)
panel.OnChildAdded = function() end
end
self.categorySubpanels[name] = panel
end
function PANEL:OnCategorySelected(name)
local panel = self.categorySubpanels[name]
if (!IsValid(panel)) then
return
end
if (!panel.bPopulated) then
self.categories[name](panel)
panel.bPopulated = true
end
if (IsValid(self.activeCategory)) then
self.activeCategory:SetVisible(false)
end
panel:SetVisible(true)
self.idlePanel:SetVisible(false)
self.activeCategory = panel
ix.gui.lastHelpMenuTab = name
end
vgui.Register("ixHelpMenu", PANEL, "EditablePanel")
local function DrawHelix(width, height, color) -- luacheck: ignore 211
local segments = 76
local radius = math.min(width, height) * 0.375
surface.SetTexture(-1)
for i = 1, math.ceil(segments) do
local angle = math.rad((i / segments) * -360)
local x = width * 0.5 + math.sin(angle + math.pi * 2) * radius
local y = height * 0.5 + math.cos(angle + math.pi * 2) * radius
local barOffset = math.sin(SysTime() + i * 0.5)
local barHeight = barOffset * radius * 0.25
if (barOffset > 0) then
surface.SetDrawColor(color)
else
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
end
surface.DrawTexturedRectRotated(x, y, 4, barHeight, math.deg(angle))
end
end
hook.Add("CreateMenuButtons", "ixHelpMenu", function(tabs)
tabs["help"] = function(container)
container:Add("ixHelpMenu")
end
end)
hook.Add("PopulateHelpMenu", "ixHelpMenu", function(tabs)
tabs["commands"] = function(container)
-- info text
local info = container:Add("DLabel")
info:SetFont("ixSmallFont")
info:SetText(L("helpCommands"))
info:SetContentAlignment(5)
info:SetTextColor(color_white)
info:SetExpensiveShadow(1, color_black)
info:Dock(TOP)
info:DockMargin(0, 0, 0, 8)
info:SizeToContents()
info:SetTall(info:GetTall() + 16)
info.Paint = function(_, width, height)
surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160))
surface.DrawRect(0, 0, width, height)
end
-- commands
for uniqueID, command in SortedPairs(ix.command.list) do
if (command.OnCheckAccess and !command:OnCheckAccess(LocalPlayer())) then
continue
end
local bIsAlias = false
local aliasText = ""
-- we want to show aliases in the same entry for better readability
if (command.alias) then
local alias = istable(command.alias) and command.alias or {command.alias}
for _, v in ipairs(alias) do
if (v:lower() == uniqueID) then
bIsAlias = true
break
end
aliasText = aliasText .. ", /" .. v
end
if (bIsAlias) then
continue
end
end
-- command name
local title = container:Add("DLabel")
title:SetFont("ixMediumLightFont")
title:SetText("/" .. command.name .. aliasText)
title:Dock(TOP)
title:SetTextColor(ix.config.Get("color"))
title:SetExpensiveShadow(1, color_black)
title:SizeToContents()
-- syntax
local syntaxText = command.syntax
local syntax
if (syntaxText != "" and syntaxText != "[none]") then
syntax = container:Add("DLabel")
syntax:SetFont("ixMediumLightFont")
syntax:SetText(syntaxText)
syntax:Dock(TOP)
syntax:SetTextColor(color_white)
syntax:SetExpensiveShadow(1, color_black)
syntax:SetWrap(true)
syntax:SetAutoStretchVertical(true)
syntax:SizeToContents()
end
-- description
local descriptionText = command:GetDescription()
if (descriptionText != "") then
local description = container:Add("DLabel")
description:SetFont("ixSmallFont")
description:SetText(descriptionText)
description:Dock(TOP)
description:SetTextColor(color_white)
description:SetExpensiveShadow(1, color_black)
description:SetWrap(true)
description:SetAutoStretchVertical(true)
description:SizeToContents()
description:DockMargin(0, 0, 0, 8)
elseif (syntax) then
syntax:DockMargin(0, 0, 0, 8)
else
title:DockMargin(0, 0, 0, 8)
end
end
end
tabs["flags"] = function(container)
-- info text
local info = container:Add("DLabel")
info:SetFont("ixSmallFont")
info:SetText(L("helpFlags"))
info:SetContentAlignment(5)
info:SetTextColor(color_white)
info:SetExpensiveShadow(1, color_black)
info:Dock(TOP)
info:DockMargin(0, 0, 0, 8)
info:SizeToContents()
info:SetTall(info:GetTall() + 16)
info.Paint = function(_, width, height)
surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160))
surface.DrawRect(0, 0, width, height)
end
-- flags
for k, v in SortedPairs(ix.flag.list) do
local background = ColorAlpha(
LocalPlayer():GetCharacter():HasFlags(k) and derma.GetColor("Success", info) or derma.GetColor("Error", info), 88
)
local panel = container:Add("Panel")
panel:Dock(TOP)
panel:DockMargin(0, 0, 0, 8)
panel:DockPadding(4, 4, 4, 4)
panel.Paint = function(_, width, height)
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, background)
end
local flag = panel:Add("DLabel")
flag:SetFont("ixMonoMediumFont")
flag:SetText(string.format("[%s]", k))
flag:Dock(LEFT)
flag:SetTextColor(color_white)
flag:SetExpensiveShadow(1, color_black)
flag:SetTextInset(4, 0)
flag:SizeToContents()
flag:SetTall(flag:GetTall() + 8)
local description = panel:Add("DLabel")
description:SetFont("ixMediumLightFont")
description:SetText(v.description)
description:Dock(FILL)
description:SetTextColor(color_white)
description:SetExpensiveShadow(1, color_black)
description:SetTextInset(8, 0)
description:SizeToContents()
description:SetTall(description:GetTall() + 8)
panel:SizeToChildren(false, true)
end
end
tabs["plugins"] = function(container)
for _, v in SortedPairsByMemberValue(ix.plugin.list, "name") do
-- name
local title = container:Add("DLabel")
title:SetFont("ixMediumLightFont")
title:SetText(v.name or "Unknown")
title:Dock(TOP)
title:SetTextColor(ix.config.Get("color"))
title:SetExpensiveShadow(1, color_black)
title:SizeToContents()
-- author
local author = container:Add("DLabel")
author:SetFont("ixSmallFont")
author:SetText(string.format("%s: %s", L("author"), v.author))
author:Dock(TOP)
author:SetTextColor(color_white)
author:SetExpensiveShadow(1, color_black)
author:SetWrap(true)
author:SetAutoStretchVertical(true)
author:SizeToContents()
-- description
local descriptionText = v.description
if (descriptionText != "") then
local description = container:Add("DLabel")
description:SetFont("ixSmallFont")
description:SetText(descriptionText)
description:Dock(TOP)
description:SetTextColor(color_white)
description:SetExpensiveShadow(1, color_black)
description:SetWrap(true)
description:SetAutoStretchVertical(true)
description:SizeToContents()
description:DockMargin(0, 0, 0, 8)
else
author:DockMargin(0, 0, 0, 8)
end
end
end
end)
================================================
FILE: gamemode/core/derma/cl_information.lua
================================================
local PANEL = {}
function PANEL:Init()
local parent = self:GetParent()
self:SetSize(parent:GetWide() * 0.6, parent:GetTall())
self:Dock(RIGHT)
self:DockMargin(0, ScrH() * 0.05, 0, 0)
self.VBar:SetWide(0)
-- entry setup
local suppress = {}
hook.Run("CanCreateCharacterInfo", suppress)
if (!suppress.time) then
local format = ix.option.Get("24hourTime", false) and "%A, %B %d, %Y. %H:%M" or "%A, %B %d, %Y. %I:%M %p"
self.time = self:Add("DLabel")
self.time:SetFont("ixMediumFont")
self.time:SetTall(28)
self.time:SetContentAlignment(5)
self.time:Dock(TOP)
self.time:SetTextColor(color_white)
self.time:SetExpensiveShadow(1, Color(0, 0, 0, 150))
self.time:DockMargin(0, 0, 0, 32)
self.time:SetText(ix.date.GetFormatted(format))
self.time.Think = function(this)
if ((this.nextTime or 0) < CurTime()) then
this:SetText(ix.date.GetFormatted(format))
this.nextTime = CurTime() + 0.5
end
end
end
if (!suppress.name) then
self.name = self:Add("ixLabel")
self.name:Dock(TOP)
self.name:DockMargin(0, 0, 0, 8)
self.name:SetFont("ixMenuButtonHugeFont")
self.name:SetContentAlignment(5)
self.name:SetTextColor(color_white)
self.name:SetPadding(8)
self.name:SetScaleWidth(true)
end
if (!suppress.description) then
self.description = self:Add("DLabel")
self.description:Dock(TOP)
self.description:DockMargin(0, 0, 0, 8)
self.description:SetFont("ixMenuButtonFont")
self.description:SetTextColor(color_white)
self.description:SetContentAlignment(5)
self.description:SetMouseInputEnabled(true)
self.description:SetCursor("hand")
self.description.Paint = function(this, width, height)
surface.SetDrawColor(0, 0, 0, 150)
surface.DrawRect(0, 0, width, height)
end
self.description.OnMousePressed = function(this, code)
if (code == MOUSE_LEFT) then
ix.command.Send("CharDesc")
if (IsValid(ix.gui.menu)) then
ix.gui.menu:Remove()
end
end
end
self.description.SizeToContents = function(this)
if (this.bWrap) then
-- sizing contents after initial wrapping does weird things so we'll just ignore (lol)
return
end
local width, height = this:GetContentSize()
if (width > self:GetWide()) then
this:SetWide(self:GetWide())
this:SetTextInset(16, 8)
this:SetWrap(true)
this:SizeToContentsY()
this:SetTall(this:GetTall() + 16) -- eh
-- wrapping doesn't like middle alignment so we'll do top-center
self.description:SetContentAlignment(8)
this.bWrap = true
else
this:SetSize(width + 16, height + 16)
end
end
end
if (!suppress.characterInfo) then
self.characterInfo = self:Add("Panel")
self.characterInfo.list = {}
self.characterInfo:Dock(TOP) -- no dock margin because this is handled by ixListRow
self.characterInfo.SizeToContents = function(this)
local height = 0
for _, v in ipairs(this:GetChildren()) do
if (IsValid(v) and v:IsVisible()) then
local _, top, _, bottom = v:GetDockMargin()
height = height + v:GetTall() + top + bottom
end
end
this:SetTall(height)
end
if (!suppress.faction) then
self.faction = self.characterInfo:Add("ixListRow")
self.faction:SetList(self.characterInfo.list)
self.faction:Dock(TOP)
end
if (!suppress.class) then
self.class = self.characterInfo:Add("ixListRow")
self.class:SetList(self.characterInfo.list)
self.class:Dock(TOP)
end
if (!suppress.money) then
self.money = self.characterInfo:Add("ixListRow")
self.money:SetList(self.characterInfo.list)
self.money:Dock(TOP)
self.money:SizeToContents()
end
hook.Run("CreateCharacterInfo", self.characterInfo)
self.characterInfo:SizeToContents()
end
-- no need to update since we aren't showing the attributes panel
if (!suppress.attributes) then
local character = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
if (character) then
self.attributes = self:Add("ixCategoryPanel")
self.attributes:SetText(L("attributes"))
self.attributes:Dock(TOP)
self.attributes:DockMargin(0, 0, 0, 8)
local boost = character:GetBoosts()
local bFirst = true
for k, v in SortedPairsByMemberValue(ix.attributes.list, "name") do
local attributeBoost = 0
if (boost[k]) then
for _, bValue in pairs(boost[k]) do
attributeBoost = attributeBoost + bValue
end
end
local bar = self.attributes:Add("ixAttributeBar")
bar:Dock(TOP)
if (!bFirst) then
bar:DockMargin(0, 3, 0, 0)
else
bFirst = false
end
local value = character:GetAttribute(k, 0)
if (attributeBoost) then
bar:SetValue(value - attributeBoost or 0)
else
bar:SetValue(value)
end
local maximum = v.maxValue or ix.config.Get("maxAttributes", 100)
bar:SetMax(maximum)
bar:SetReadOnly()
bar:SetText(Format("%s [%.1f/%.1f] (%.1f%%)", L(v.name), value, maximum, value / maximum * 100))
if (attributeBoost) then
bar:SetBoost(attributeBoost)
end
end
self.attributes:SizeToContents()
end
end
hook.Run("CreateCharacterInfoCategory", self)
end
function PANEL:Update(character)
if (!character) then
return
end
local faction = ix.faction.indices[character:GetFaction()]
local class = ix.class.list[character:GetClass()]
if (self.name) then
self.name:SetText(character:GetName())
if (faction) then
self.name.backgroundColor = ColorAlpha(faction.color, 150) or Color(0, 0, 0, 150)
end
self.name:SizeToContents()
end
if (self.description) then
self.description:SetText(character:GetDescription())
self.description:SizeToContents()
end
if (self.faction) then
self.faction:SetLabelText(L("faction"))
self.faction:SetText(L(faction.name))
self.faction:SizeToContents()
end
if (self.class) then
-- don't show class label if the class is the same name as the faction
if (class and class.name != faction.name) then
self.class:SetLabelText(L("class"))
self.class:SetText(L(class.name))
self.class:SizeToContents()
else
self.class:SetVisible(false)
end
end
if (self.money) then
self.money:SetLabelText(L("money"))
self.money:SetText(ix.currency.Get(character:GetMoney()))
self.money:SizeToContents()
end
hook.Run("UpdateCharacterInfo", self.characterInfo, character)
self.characterInfo:SizeToContents()
hook.Run("UpdateCharacterInfoCategory", self, character)
end
function PANEL:OnSubpanelRightClick()
properties.OpenEntityMenu(LocalPlayer())
end
vgui.Register("ixCharacterInfo", PANEL, "DScrollPanel")
hook.Add("CreateMenuButtons", "ixCharInfo", function(tabs)
tabs["you"] = {
bHideBackground = true,
buttonColor = team.GetColor(LocalPlayer():Team()),
Create = function(info, container)
container.infoPanel = container:Add("ixCharacterInfo")
container.OnMouseReleased = function(this, key)
if (key == MOUSE_RIGHT) then
this.infoPanel:OnSubpanelRightClick()
end
end
end,
OnSelected = function(info, container)
container.infoPanel:Update(LocalPlayer():GetCharacter())
ix.gui.menu:SetCharacterOverview(true)
end,
OnDeselected = function(info, container)
ix.gui.menu:SetCharacterOverview(false)
end
}
end)
================================================
FILE: gamemode/core/derma/cl_intro.lua
================================================
local waveSegments = 32
local helixSegments = 76
local helixHeight = 64
local backgroundColor = Color(115, 53, 142)
local dimColor = Color(165, 134, 179)
DEFINE_BASECLASS("EditablePanel")
local PANEL = {}
function PANEL:Init()
if (IsValid(ix.gui.intro)) then
ix.gui.intro:Remove()
end
ix.gui.intro = self
self:SetSize(ScrW(), ScrH())
self:SetPos(0, 0)
self:SetZPos(99999)
self:MakePopup()
-- animation parameters
self.bBackground = true
self.volume = 1
self.sunbeamOffset = 0
self.textOne = 0
self.textTwo = 0
self.kickTarget = 0
self.helix = 0
self.helixAlpha = 0
self.continueText = 0
self.pulse = 0
self.waves = {
{1.1, 0},
{1.1, math.pi},
{1.1, math.pi * 1.6},
{1.1, math.pi * 0.5}
}
end
-- @todo h a c k
function PANEL:Think()
if (IsValid(LocalPlayer())) then
self:BeginIntro()
self.Think = nil
end
end
function PANEL:BeginIntro()
-- something could have errored on startup and invalidated all options, so we'll be extra careful with setting the option
-- because if it errors here, the sound will play each tick and proceed to hurt ears
local bLoaded = false
if (ix and ix.option and ix.option.Set) then
local bSuccess, _ = pcall(ix.option.Set, "showIntro", false)
bLoaded = bSuccess
end
if (!bLoaded) then
self:Remove()
if (ix and ix.gui and IsValid(ix.gui.characterMenu)) then
ix.gui.characterMenu:Remove()
end
ErrorNoHalt(
"[Helix] Something has errored and prevented the framework from loading correctly - check your console for errors!\n")
return
end
self:MoveToFront()
self:RequestFocus()
sound.PlayFile("sound/buttons/combine_button2.wav", "", function()
timer.Create("ixIntroStart", 2, 1, function()
sound.PlayFile("sound/helix/intro.mp3", "", function(channel, status, error)
if (IsValid(channel)) then
channel:SetVolume(self.volume)
self.channel = channel
end
self:BeginAnimation()
end)
end)
end)
end
function PANEL:AnimateWaves(target, bReverse)
for i = bReverse and #self.waves or 1,
bReverse and 1 or #self.waves,
bReverse and -1 or 1 do
local animation = self:CreateAnimation(2, {
index = 20 + (bReverse and (#self.waves - i) or i),
bAutoFire = false,
target = {
waves = {
[i] = {target}
}
},
easing = bReverse and "inQuart" or "outQuint"
})
timer.Simple((bReverse and (#self.waves - i) or i) * 0.1, function()
if (IsValid(self) and animation) then
animation:Fire()
end
end)
-- return last animation that plays
if ((bReverse and i == 1) or (!bReverse and i == #self.waves)) then
return animation
end
end
end
function PANEL:BeginAnimation()
self:CreateAnimation(2, {
target = {textOne = 1},
easing = "inQuint",
bIgnoreConfig = true
})
:CreateAnimation(2, {
target = {textOne = 0},
easing = "inQuint",
bIgnoreConfig = true
})
:CreateAnimation(2, {
target = {textTwo = 1},
easing = "inQuint",
bIgnoreConfig = true,
OnComplete = function(animation, panel)
self:AnimateWaves(0)
end
})
:CreateAnimation(2, {
target = {textTwo = 0},
easing = "inQuint",
bIgnoreConfig = true
})
:CreateAnimation(4, {
target = {sunbeamOffset = 1},
bIgnoreConfig = true,
OnComplete = function()
self:CreateAnimation(2,{
target = {helixAlpha = 1},
easing = "inCubic"
})
end
})
:CreateAnimation(2, {
target = {helix = 1},
easing = "outQuart",
bIgnoreConfig = true
})
:CreateAnimation(2, {
target = {continueText = 1},
easing = "linear",
bIgnoreConfig = true
})
end
function PANEL:PaintCurve(y, width, offset, scale)
offset = offset or 1
scale = scale or 32
local points = {
[1] = {
x = 0,
y = ScrH()
}
}
for i = 0, waveSegments do
local angle = math.rad((i / waveSegments) * -360)
points[#points + 1] = {
x = (width / waveSegments) * i,
y = y + (math.sin(angle * 0.5 + offset) - 1) * scale
}
end
points[#points + 1] = {
x = width,
y = ScrH()
}
draw.NoTexture()
surface.DrawPoly(points)
end
function PANEL:Paint(width, height)
local time = SysTime()
local text = L("helix"):lower()
local centerY = height * self.waves[#self.waves][1] + height * 0.5
local sunbeamOffsetEasing = math.sin(math.pi * self.sunbeamOffset)
local textWidth, textHeight
local fft
-- background
if (self.bBackground) then
surface.SetDrawColor(0, 0, 0, 255)
surface.DrawRect(0, 0, width, height)
end
if (self.sunbeamOffset == 1) then
fft = {}
if (IsValid(self.channel)) then
self.channel:FFT(fft, FFT_2048)
local kick = (fft[4] or 0) * 8192
self.kickTarget = math.Approach(self.kickTarget, kick, 8 * math.abs(kick - self.kickTarget) * FrameTime())
end
end
-- waves
for i = 1, #self.waves do
local wave = self.waves[i]
local ratio = i / #self.waves
local color = Color(
backgroundColor.r * ratio,
backgroundColor.g * ratio,
backgroundColor.b * ratio,
self.bBackground and 255 or (ratio * 320)
)
surface.SetDrawColor(color)
self:PaintCurve(height * wave[1], width, wave[2])
end
-- helix
if (self.helix > 0) then
local alpha = self.helixAlpha * 255
derma.SkinFunc("DrawHelixCurved",
width * 0.5, centerY,
math.min(ScreenScale(72), 128) * 2, -- font sizes are clamped to 128
helixSegments * self.helix, helixHeight, self.helix,
ColorAlpha(color_white, alpha),
ColorAlpha(dimColor, alpha)
)
end
-- title text glow
surface.SetTextColor(255, 255, 255,
self.sunbeamOffset == 1 and self.kickTarget or sunbeamOffsetEasing * 255
)
surface.SetFont("ixIntroTitleBlurFont")
local logoTextWidth, logoTextHeight = surface.GetTextSize(text)
surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5)
surface.DrawText(text)
-- title text
surface.SetTextColor(255, 255, 255, self.sunbeamOffset * 255)
surface.SetFont("ixIntroTitleFont")
logoTextWidth, logoTextHeight = surface.GetTextSize(text)
surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5)
surface.DrawText(text)
-- text one
surface.SetFont("ixIntroSubtitleFont")
text = L("introTextOne"):lower()
textWidth = surface.GetTextSize(text)
surface.SetTextColor(255, 255, 255, self.textOne * 255)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66)
surface.DrawText(text)
-- text two
text = L("introTextTwo", Schema.author or "nebulous"):lower()
textWidth = surface.GetTextSize(text)
surface.SetTextColor(255, 255, 255, self.textTwo * 255)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66)
surface.DrawText(text)
-- continue text
surface.SetFont("ixIntroSmallFont")
text = L("introContinue"):lower()
textWidth, textHeight = surface.GetTextSize(text)
if (self.continueText == 1) then
self.pulse = self.pulse + 6 * FrameTime()
if (self.pulse >= 360) then
self.pulse = 0
end
end
surface.SetTextColor(255, 255, 255, self.continueText * 255 - (math.sin(self.pulse) * 100), 0)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, centerY * 2 - textHeight * 2)
surface.DrawText(text)
-- sunbeams
if (self.sunbeamOffset > 0 and self.sunbeamOffset != 1) then
DrawSunbeams(0.25, sunbeamOffsetEasing * 0.1, 0.02,
(((width * 0.5 - logoTextWidth * 0.5) - 32) / width) + ((logoTextWidth + 64) / width) * self.sunbeamOffset,
0.5 + math.sin(time * 2) * 0.01
)
end
end
function PANEL:OnKeyCodePressed(key)
if (key == KEY_SPACE and self.continueText > 0.25) then
self:Remove()
end
end
function PANEL:OnRemove()
timer.Remove("ixIntroStart")
if (IsValid(self.channel)) then
self.channel:Stop()
end
if (IsValid(ix.gui.characterMenu)) then
ix.gui.characterMenu:PlayMusic()
end
end
function PANEL:Remove(bForce)
if (bForce) then
BaseClass.Remove(self)
return
end
if (self.bClosing) then
return
end
self.bClosing = true
self.bBackground = nil
-- waves
local animation = self:AnimateWaves(1.1, true)
animation.OnComplete = function(anim, panel)
panel:SetMouseInputEnabled(false)
panel:SetKeyboardInputEnabled(false)
end
-- audio
self:CreateAnimation(4.5, {
index = 1,
target = {volume = 0},
Think = function(anim, panel)
if (IsValid(panel.channel)) then
panel.channel:SetVolume(panel.volume)
end
end,
OnComplete = function()
timer.Simple(0, function()
BaseClass.Remove(self)
end)
end
})
end
vgui.Register("ixIntro", PANEL, "EditablePanel")
================================================
FILE: gamemode/core/derma/cl_inventory.lua
================================================
local RECEIVER_NAME = "ixInventoryItem"
-- The queue for the rendered icons.
ICON_RENDER_QUEUE = ICON_RENDER_QUEUE or {}
-- To make making inventory variant, This must be followed up.
local function RenderNewIcon(panel, itemTable)
local model = itemTable:GetModel()
-- re-render icons
if ((itemTable.iconCam and !ICON_RENDER_QUEUE[string.lower(model)]) or itemTable.forceRender) then
local iconCam = itemTable.iconCam
iconCam = {
cam_pos = iconCam.pos,
cam_ang = iconCam.ang,
cam_fov = iconCam.fov,
}
ICON_RENDER_QUEUE[string.lower(model)] = true
panel.Icon:RebuildSpawnIconEx(
iconCam
)
end
end
local function InventoryAction(action, itemID, invID, data)
net.Start("ixInventoryAction")
net.WriteString(action)
net.WriteUInt(itemID, 32)
net.WriteUInt(invID, 32)
net.WriteTable(data or {})
net.SendToServer()
end
local PANEL = {}
AccessorFunc(PANEL, "itemTable", "ItemTable")
AccessorFunc(PANEL, "inventoryID", "InventoryID")
function PANEL:Init()
self:Droppable(RECEIVER_NAME)
end
function PANEL:OnMousePressed(code)
if (code == MOUSE_LEFT and self:IsDraggable()) then
self:MouseCapture(true)
self:DragMousePress(code)
self.clickX, self.clickY = input.GetCursorPos()
elseif (code == MOUSE_RIGHT and self.DoRightClick) then
self:DoRightClick()
end
end
function PANEL:OnMouseReleased(code)
-- move the item into the world if we're dropping on something that doesn't handle inventory item drops
if (!dragndrop.m_ReceiverSlot or dragndrop.m_ReceiverSlot.Name != RECEIVER_NAME) then
self:OnDrop(dragndrop.IsDragging())
end
self:DragMouseRelease(code)
self:SetZPos(99)
self:MouseCapture(false)
end
function PANEL:DoRightClick()
local itemTable = self.itemTable
local inventory = self.inventoryID
if (itemTable and inventory) then
itemTable.player = LocalPlayer()
local menu = DermaMenu()
local override = hook.Run("CreateItemInteractionMenu", self, menu, itemTable)
if (override == true) then
if (menu.Remove) then
menu:Remove()
end
return
end
for k, v in SortedPairs(itemTable.functions) do
if (k == "drop" or k == "combine" or (v.OnCanRun and v.OnCanRun(itemTable) == false)) then
continue
end
-- is Multi-Option Function
if (v.isMulti) then
local subMenu, subMenuOption = menu:AddSubMenu(L(v.name or k), function()
itemTable.player = LocalPlayer()
local send = true
if (v.OnClick) then
send = v.OnClick(itemTable)
end
if (v.sound) then
surface.PlaySound(v.sound)
end
if (send != false) then
InventoryAction(k, itemTable.id, inventory)
end
itemTable.player = nil
end)
subMenuOption:SetImage(v.icon or "icon16/brick.png")
if (v.multiOptions) then
local options = isfunction(v.multiOptions) and v.multiOptions(itemTable, LocalPlayer()) or v.multiOptions
for _, sub in pairs(options) do
subMenu:AddOption(L(sub.name or "subOption"), function()
itemTable.player = LocalPlayer()
local send = true
if (sub.OnClick) then
send = sub.OnClick(itemTable)
end
if (sub.sound) then
surface.PlaySound(sub.sound)
end
if (send != false) then
InventoryAction(k, itemTable.id, inventory, sub.data)
end
itemTable.player = nil
end)
end
end
else
menu:AddOption(L(v.name or k), function()
itemTable.player = LocalPlayer()
local send = true
if (v.OnClick) then
send = v.OnClick(itemTable)
end
if (v.sound) then
surface.PlaySound(v.sound)
end
if (send != false) then
InventoryAction(k, itemTable.id, inventory)
end
itemTable.player = nil
end):SetImage(v.icon or "icon16/brick.png")
end
end
-- we want drop to show up as the last option
local info = itemTable.functions.drop
if (info and info.OnCanRun and info.OnCanRun(itemTable) != false) then
menu:AddOption(L(info.name or "drop"), function()
itemTable.player = LocalPlayer()
local send = true
if (info.OnClick) then
send = info.OnClick(itemTable)
end
if (info.sound) then
surface.PlaySound(info.sound)
end
if (send != false) then
InventoryAction("drop", itemTable.id, inventory)
end
itemTable.player = nil
end):SetImage(info.icon or "icon16/brick.png")
end
menu:Open()
itemTable.player = nil
end
end
function PANEL:OnDrop(bDragging, inventoryPanel, inventory, gridX, gridY)
local item = self.itemTable
if (!item or !bDragging) then
return
end
if (!IsValid(inventoryPanel)) then
local inventoryID = self.inventoryID
if (inventoryID) then
InventoryAction("drop", item.id, inventoryID, {})
end
elseif (inventoryPanel:IsAllEmpty(gridX, gridY, item.width, item.height, self)) then
local oldX, oldY = self.gridX, self.gridY
if (oldX != gridX or oldY != gridY or self.inventoryID != inventoryPanel.invID) then
self:Move(gridX, gridY, inventoryPanel)
end
elseif (inventoryPanel.combineItem) then
local combineItem = inventoryPanel.combineItem
local inventoryID = combineItem.invID
if (inventoryID) then
combineItem.player = LocalPlayer()
if (combineItem.functions.combine.sound) then
surface.PlaySound(combineItem.functions.combine.sound)
end
InventoryAction("combine", combineItem.id, inventoryID, {item.id})
combineItem.player = nil
end
end
end
function PANEL:Move(newX, newY, givenInventory, bNoSend)
local iconSize = givenInventory.iconSize
local oldX, oldY = self.gridX, self.gridY
local oldParent = self:GetParent()
if (givenInventory:OnTransfer(oldX, oldY, newX, newY, oldParent, bNoSend) == false) then
return
end
local x = (newX - 1) * iconSize + 4
local y = (newY - 1) * iconSize + givenInventory:GetPadding(2)
self.gridX = newX
self.gridY = newY
self:SetParent(givenInventory)
self:SetPos(x, y)
if (self.slots) then
for _, v in ipairs(self.slots) do
if (IsValid(v) and v.item == self) then
v.item = nil
end
end
end
self.slots = {}
for currentX = 1, self.gridW do
for currentY = 1, self.gridH do
local slot = givenInventory.slots[self.gridX + currentX - 1][self.gridY + currentY - 1]
slot.item = self
self.slots[#self.slots + 1] = slot
end
end
end
function PANEL:PaintOver(width, height)
local itemTable = self.itemTable
if (itemTable and itemTable.PaintOver) then
itemTable.PaintOver(self, itemTable, width, height)
end
end
function PANEL:ExtraPaint(width, height)
end
function PANEL:Paint(width, height)
surface.SetDrawColor(0, 0, 0, 85)
surface.DrawRect(2, 2, width - 4, height - 4)
self:ExtraPaint(width, height)
end
vgui.Register("ixItemIcon", PANEL, "SpawnIcon")
PANEL = {}
DEFINE_BASECLASS("DFrame")
AccessorFunc(PANEL, "iconSize", "IconSize", FORCE_NUMBER)
AccessorFunc(PANEL, "bHighlighted", "Highlighted", FORCE_BOOL)
function PANEL:Init()
self:SetIconSize(64)
self:ShowCloseButton(false)
self:SetDraggable(true)
self:SetSizable(true)
self:SetTitle(L"inv")
self:Receiver(RECEIVER_NAME, self.ReceiveDrop)
self.btnMinim:SetVisible(false)
self.btnMinim:SetMouseInputEnabled(false)
self.btnMaxim:SetVisible(false)
self.btnMaxim:SetMouseInputEnabled(false)
self.panels = {}
end
function PANEL:GetPadding(index)
return select(index, self:GetDockPadding())
end
function PANEL:SetTitle(text)
if (text == nil) then
self.oldPadding = {self:GetDockPadding()}
self.lblTitle:SetText("")
self.lblTitle:SetVisible(false)
self:DockPadding(5, 5, 5, 5)
else
if (self.oldPadding) then
self:DockPadding(unpack(self.oldPadding))
self.oldPadding = nil
end
BaseClass.SetTitle(self, text)
end
end
function PANEL:FitParent(invWidth, invHeight)
local parent = self:GetParent()
if (!IsValid(parent)) then
return
end
local width, height = parent:GetSize()
local padding = 4
local iconSize
if (invWidth > invHeight) then
iconSize = (width - padding * 2) / invWidth
elseif (invHeight > invWidth) then
iconSize = (height - padding * 2) / invHeight
else
-- we use height because the titlebar will make it more tall than it is wide
iconSize = (height - padding * 2) / invHeight - 4
end
self:SetSize(iconSize * invWidth + padding * 2, iconSize * invHeight + padding * 2)
self:SetIconSize(iconSize)
end
function PANEL:OnRemove()
if (self.childPanels) then
for _, v in ipairs(self.childPanels) do
if (v != self) then
v:Remove()
end
end
end
end
function PANEL:ViewOnly()
self.viewOnly = true
for _, icon in pairs(self.panels) do
icon.OnMousePressed = nil
icon.OnMouseReleased = nil
icon.doRightClick = nil
end
end
function PANEL:SetInventory(inventory, bFitParent)
if (inventory.slots) then
local invWidth, invHeight = inventory:GetSize()
self.invID = inventory:GetID()
if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels and inventory != LocalPlayer():GetCharacter():GetInventory()) then
self:SetIconSize(ix.gui.inv1:GetIconSize())
self:SetPaintedManually(true)
self.bNoBackgroundBlur = true
ix.gui.inv1.childPanels[#ix.gui.inv1.childPanels + 1] = self
elseif (bFitParent) then
self:FitParent(invWidth, invHeight)
else
self:SetSize(self.iconSize, self.iconSize)
end
self:SetGridSize(invWidth, invHeight)
for x, items in pairs(inventory.slots) do
for y, data in pairs(items) do
if (!data.id) then continue end
local item = ix.item.instances[data.id]
if (item and !IsValid(self.panels[item.id])) then
local icon = self:AddIcon(item:GetModel() or "models/props_junk/popcan01a.mdl",
x, y, item.width, item.height, item:GetSkin())
if (IsValid(icon)) then
icon:SetHelixTooltip(function(tooltip)
ix.hud.PopulateItemTooltip(tooltip, item)
end)
self.panels[item.id] = icon
end
end
end
end
end
end
function PANEL:SetGridSize(w, h)
local iconSize = self.iconSize
local newWidth = w * iconSize + 8
local newHeight = h * iconSize + self:GetPadding(2) + self:GetPadding(4)
self.gridW = w
self.gridH = h
self:SetSize(newWidth, newHeight)
self:SetMinWidth(newWidth)
self:SetMinHeight(newHeight)
self:BuildSlots()
end
function PANEL:PerformLayout(width, height)
BaseClass.PerformLayout(self, width, height)
if (self.Sizing and self.gridW and self.gridH) then
local newWidth = (width - 8) / self.gridW
local newHeight = (height - self:GetPadding(2) + self:GetPadding(4)) / self.gridH
self:SetIconSize((newWidth + newHeight) / 2)
self:RebuildItems()
end
end
function PANEL:BuildSlots()
local iconSize = self.iconSize
self.slots = self.slots or {}
for _, v in ipairs(self.slots) do
for _, v2 in ipairs(v) do
v2:Remove()
end
end
self.slots = {}
for x = 1, self.gridW do
self.slots[x] = {}
for y = 1, self.gridH do
local slot = self:Add("DPanel")
slot:SetZPos(-999)
slot.gridX = x
slot.gridY = y
slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2))
slot:SetSize(iconSize, iconSize)
slot.Paint = function(panel, width, height)
derma.SkinFunc("PaintInventorySlot", panel, width, height)
end
self.slots[x][y] = slot
end
end
end
function PANEL:RebuildItems()
local iconSize = self.iconSize
for x = 1, self.gridW do
for y = 1, self.gridH do
local slot = self.slots[x][y]
slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2))
slot:SetSize(iconSize, iconSize)
end
end
for _, v in pairs(self.panels) do
if (IsValid(v)) then
v:SetPos(self.slots[v.gridX][v.gridY]:GetPos())
v:SetSize(v.gridW * iconSize, v.gridH * iconSize)
end
end
end
function PANEL:PaintDragPreview(width, height, mouseX, mouseY, itemPanel)
local iconSize = self.iconSize
local item = itemPanel:GetItemTable()
if (item) then
local inventory = ix.item.inventories[self.invID]
local dropX = math.ceil((mouseX - 4 - (itemPanel.gridW - 1) * 32) / iconSize)
local dropY = math.ceil((mouseY - self:GetPadding(2) - (itemPanel.gridH - 1) * 32) / iconSize)
local hoveredPanel = vgui.GetHoveredPanel()
if (IsValid(hoveredPanel) and hoveredPanel != itemPanel and hoveredPanel.GetItemTable) then
local hoveredItem = hoveredPanel:GetItemTable()
if (hoveredItem) then
local info = hoveredItem.functions.combine
if (info and info.OnCanRun and info.OnCanRun(hoveredItem, {item.id}) != false) then
surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", self, Color(200, 0, 0)), 20))
surface.DrawRect(
hoveredPanel.x,
hoveredPanel.y,
hoveredPanel:GetWide(),
hoveredPanel:GetTall()
)
self.combineItem = hoveredItem
return
end
end
end
self.combineItem = nil
-- don't draw grid if we're dragging it out of bounds
if (inventory) then
local invWidth, invHeight = inventory:GetSize()
if (dropX < 1 or dropY < 1 or
dropX + itemPanel.gridW - 1 > invWidth or
dropY + itemPanel.gridH - 1 > invHeight) then
return
end
end
local bEmpty = true
for x = 0, itemPanel.gridW - 1 do
for y = 0, itemPanel.gridH - 1 do
local x2 = dropX + x
local y2 = dropY + y
bEmpty = self:IsEmpty(x2, y2, itemPanel)
if (!bEmpty) then
-- no need to iterate further since we know something is blocking the hovered grid cells, break through both loops
goto finish
end
end
end
::finish::
local previewColor = ColorAlpha(derma.GetColor(bEmpty and "Success" or "Error", self, Color(200, 0, 0)), 20)
surface.SetDrawColor(previewColor)
surface.DrawRect(
(dropX - 1) * iconSize + 4,
(dropY - 1) * iconSize + self:GetPadding(2),
itemPanel:GetWide(),
itemPanel:GetTall()
)
end
end
function PANEL:PaintOver(width, height)
local panel = self.previewPanel
if (IsValid(panel)) then
local itemPanel = (dragndrop.GetDroppable() or {})[1]
if (IsValid(itemPanel)) then
self:PaintDragPreview(width, height, self.previewX, self.previewY, itemPanel)
end
end
self.previewPanel = nil
end
function PANEL:IsEmpty(x, y, this)
return (self.slots[x] and self.slots[x][y]) and (!IsValid(self.slots[x][y].item) or self.slots[x][y].item == this)
end
function PANEL:IsAllEmpty(x, y, width, height, this)
for x2 = 0, width - 1 do
for y2 = 0, height - 1 do
if (!self:IsEmpty(x + x2, y + y2, this)) then
return false
end
end
end
return true
end
function PANEL:OnTransfer(oldX, oldY, x, y, oldInventory, noSend)
local inventories = ix.item.inventories
local inventory = inventories[oldInventory.invID]
local inventory2 = inventories[self.invID]
local item
if (inventory) then
item = inventory:GetItemAt(oldX, oldY)
if (!item) then
return false
end
if (hook.Run("CanTransferItem", item, inventories[oldInventory.invID], inventories[self.invID]) == false) then
return false, "notAllowed"
end
if (item.CanTransfer and
item:CanTransfer(inventory, inventory != inventory2 and inventory2 or nil) == false) then
return false
end
end
if (!noSend) then
net.Start("ixInventoryMove")
net.WriteUInt(oldX, 6)
net.WriteUInt(oldY, 6)
net.WriteUInt(x, 6)
net.WriteUInt(y, 6)
net.WriteUInt(oldInventory.invID, 32)
net.WriteUInt(self != oldInventory and self.invID or oldInventory.invID, 32)
net.SendToServer()
end
if (inventory) then
inventory.slots[oldX][oldY] = nil
end
if (item and inventory2) then
inventory2.slots[x] = inventory2.slots[x] or {}
inventory2.slots[x][y] = item
end
end
function PANEL:AddIcon(model, x, y, w, h, skin)
local iconSize = self.iconSize
w = w or 1
h = h or 1
if (self.slots[x] and self.slots[x][y]) then
local panel = self:Add("ixItemIcon")
panel:SetSize(w * iconSize, h * iconSize)
panel:SetZPos(999)
panel:InvalidateLayout(true)
panel:SetModel(model, skin)
panel:SetPos(self.slots[x][y]:GetPos())
panel.gridX = x
panel.gridY = y
panel.gridW = w
panel.gridH = h
local inventory = ix.item.inventories[self.invID]
if (!inventory) then
return
end
local itemTable = inventory:GetItemAt(panel.gridX, panel.gridY)
panel:SetInventoryID(inventory:GetID())
panel:SetItemTable(itemTable)
if (self.panels[itemTable:GetID()]) then
self.panels[itemTable:GetID()]:Remove()
end
if (itemTable.exRender) then
panel.Icon:SetVisible(false)
panel.ExtraPaint = function(this, panelX, panelY)
local exIcon = ikon:GetIcon(itemTable.uniqueID)
if (exIcon) then
surface.SetMaterial(exIcon)
surface.SetDrawColor(color_white)
surface.DrawTexturedRect(0, 0, panelX, panelY)
else
ikon:renderIcon(
itemTable.uniqueID,
itemTable.width,
itemTable.height,
itemTable:GetModel(),
itemTable.iconCam
)
end
end
else
-- yeah..
RenderNewIcon(panel, itemTable)
end
panel.slots = {}
for i = 0, w - 1 do
for i2 = 0, h - 1 do
local slot = self.slots[x + i] and self.slots[x + i][y + i2]
if (IsValid(slot)) then
slot.item = panel
panel.slots[#panel.slots + 1] = slot
else
for _, v in ipairs(panel.slots) do
v.item = nil
end
panel:Remove()
return
end
end
end
return panel
end
end
function PANEL:ReceiveDrop(panels, bDropped, menuIndex, x, y)
local panel = panels[1]
if (!IsValid(panel)) then
self.previewPanel = nil
return
end
if (bDropped) then
local inventory = ix.item.inventories[self.invID]
if (inventory and panel.OnDrop) then
local dropX = math.ceil((x - 4 - (panel.gridW - 1) * 32) / self.iconSize)
local dropY = math.ceil((y - self:GetPadding(2) - (panel.gridH - 1) * 32) / self.iconSize)
panel:OnDrop(true, self, inventory, dropX, dropY)
end
self.previewPanel = nil
else
self.previewPanel = panel
self.previewX = x
self.previewY = y
end
end
vgui.Register("ixInventory", PANEL, "DFrame")
hook.Add("CreateMenuButtons", "ixInventory", function(tabs)
if (hook.Run("CanPlayerViewInventory") == false) then
return
end
tabs["inv"] = {
bDefault = true,
Create = function(info, container)
local canvas = container:Add("DTileLayout")
local canvasLayout = canvas.PerformLayout
canvas.PerformLayout = nil -- we'll layout after we add the panels instead of each time one is added
canvas:SetBorder(0)
canvas:SetSpaceX(2)
canvas:SetSpaceY(2)
canvas:Dock(FILL)
ix.gui.menuInventoryContainer = canvas
local panel = canvas:Add("ixInventory")
panel:SetPos(0, 0)
panel:SetDraggable(false)
panel:SetSizable(false)
panel:SetTitle(nil)
panel.bNoBackgroundBlur = true
panel.childPanels = {}
local inventory = LocalPlayer():GetCharacter():GetInventory()
if (inventory) then
panel:SetInventory(inventory)
end
ix.gui.inv1 = panel
if (ix.option.Get("openBags", true)) then
for k, _ in inventory:Iter() do
if (!k.isBag) then
continue
end
k.functions.View.OnClick(k)
end
end
canvas.PerformLayout = canvasLayout
canvas:Layout()
end
}
end)
hook.Add("PostRenderVGUI", "ixInvHelper", function()
local pnl = ix.gui.inv1
hook.Run("PostDrawInventory", pnl)
end)
================================================
FILE: gamemode/core/derma/cl_menu.lua
================================================
local animationTime = 1
local matrixZScale = Vector(1, 1, 0.0001)
DEFINE_BASECLASS("ixSubpanelParent")
local PANEL = {}
AccessorFunc(PANEL, "bCharacterOverview", "CharacterOverview", FORCE_BOOL)
function PANEL:Init()
if (IsValid(ix.gui.menu)) then
ix.gui.menu:Remove()
end
ix.gui.menu = self
-- properties
self.manualChildren = {}
self.noAnchor = CurTime() + 0.4
self.anchorMode = true
self.rotationOffset = Angle(0, 180, 0)
self.projectedTexturePosition = Vector(0, 0, 6)
self.projectedTextureRotation = Angle(-45, 60, 0)
self.bCharacterOverview = false
self.bOverviewOut = false
self.overviewFraction = 0
self.currentAlpha = 0
self.currentBlur = 0
-- setup
self:SetPadding(ScreenScale(16), true)
self:SetSize(ScrW(), ScrH())
self:SetPos(0, 0)
self:SetLeftOffset(self:GetWide() * 0.25 + self:GetPadding())
-- main button panel
self.buttons = self:Add("Panel")
self.buttons:SetSize(self:GetWide() * 0.25, self:GetTall() - self:GetPadding() * 2)
self.buttons:Dock(LEFT)
self.buttons:SetPaintedManually(true)
local close = self.buttons:Add("ixMenuButton")
close:SetText("return")
close:SizeToContents()
close:Dock(BOTTOM)
close.DoClick = function()
self:Remove()
end
local characters = self.buttons:Add("ixMenuButton")
characters:SetText("characters")
characters:SizeToContents()
characters:Dock(BOTTOM)
characters.DoClick = function()
self:Remove()
vgui.Create("ixCharMenu")
end
-- @todo make a better way to avoid clicks in the padding PLEASE
self.guard = self:Add("Panel")
self.guard:SetPos(0, 0)
self.guard:SetSize(self:GetPadding(), self:GetTall())
-- tabs
self.tabs = self.buttons:Add("DScrollPanel")
self.tabs.buttons = {}
self.tabs:Dock(FILL)
self:PopulateTabs()
self:MakePopup()
self:OnOpened()
end
function PANEL:OnOpened()
self:SetAlpha(0)
self:CreateAnimation(animationTime, {
target = {currentAlpha = 255},
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(panel.currentAlpha)
end
})
end
function PANEL:GetActiveTab()
return (self:GetActiveSubpanel() or {}).subpanelName
end
function PANEL:TransitionSubpanel(id)
local lastSubpanel = self:GetActiveSubpanel()
-- don't transition to the same panel
if (IsValid(lastSubpanel) and lastSubpanel.subpanelID == id) then
return
end
local subpanel = self:GetSubpanel(id)
if (IsValid(subpanel)) then
if (!subpanel.bPopulated) then
-- we need to set the size of the subpanel if it's a section since it will be 0, 0
if (subpanel.sectionParent) then
subpanel:SetSize(self:GetStandardSubpanelSize())
end
local info = subpanel.info
subpanel.Paint = nil
if (istable(info) and info.Create) then
info:Create(subpanel)
elseif (isfunction(info)) then
info(subpanel)
end
hook.Run("MenuSubpanelCreated", subpanel.subpanelName, subpanel)
subpanel.bPopulated = true
end
-- only play whoosh sound only when the menu was already open
if (IsValid(lastSubpanel)) then
LocalPlayer():EmitSound("Helix.Whoosh")
end
self:SetActiveSubpanel(id)
end
subpanel = self:GetActiveSubpanel()
local info = subpanel.info
local bHideBackground = istable(info) and (info.bHideBackground != nil and info.bHideBackground or false) or false
if (bHideBackground) then
self:HideBackground()
else
self:ShowBackground()
end
-- call hooks if we've changed subpanel
if (IsValid(lastSubpanel) and istable(lastSubpanel.info) and lastSubpanel.info.OnDeselected) then
lastSubpanel.info:OnDeselected(lastSubpanel)
end
if (IsValid(subpanel) and istable(subpanel.info) and subpanel.info.OnSelected) then
subpanel.info:OnSelected(subpanel)
end
ix.gui.lastMenuTab = id
end
function PANEL:SetCharacterOverview(bValue, length)
bValue = tobool(bValue)
length = length or animationTime
if (bValue) then
if (!IsValid(self.projectedTexture)) then
self.projectedTexture = ProjectedTexture()
end
local faction = ix.faction.indices[LocalPlayer():Team()]
local color = faction and faction.color or color_white
self.projectedTexture:SetEnableShadows(false)
self.projectedTexture:SetNearZ(12)
self.projectedTexture:SetFarZ(64)
self.projectedTexture:SetFOV(90)
self.projectedTexture:SetColor(color)
self.projectedTexture:SetTexture("effects/flashlight/soft")
self:CreateAnimation(length, {
index = 3,
target = {overviewFraction = 1},
easing = "outQuint",
bIgnoreConfig = true
})
self.bOverviewOut = false
self.bCharacterOverview = true
else
self:CreateAnimation(length, {
index = 3,
target = {overviewFraction = 0},
easing = "outQuint",
bIgnoreConfig = true,
OnComplete = function(animation, panel)
panel.bCharacterOverview = false
if (IsValid(panel.projectedTexture)) then
panel.projectedTexture:Remove()
end
end
})
self.bOverviewOut = true
end
end
function PANEL:GetOverviewInfo(origin, angles, fov)
local originAngles = Angle(0, angles.yaw, angles.roll)
local target = LocalPlayer():GetObserverTarget()
local fraction = self.overviewFraction
local bDrawPlayer = ((fraction > 0.2) or (!self.bOverviewOut and (fraction > 0.2))) and !IsValid(target)
local forward = originAngles:Forward() * 58 - originAngles:Right() * 16
forward.z = 0
local newOrigin
if (IsValid(target)) then
newOrigin = target:GetPos() + forward
else
newOrigin = origin - LocalPlayer():OBBCenter() * 0.6 + forward
end
local newAngles = originAngles + self.rotationOffset
newAngles.pitch = 5
newAngles.roll = 0
return LerpVector(fraction, origin, newOrigin), LerpAngle(fraction, angles, newAngles), Lerp(fraction, fov, 90), bDrawPlayer
end
function PANEL:HideBackground()
self:CreateAnimation(animationTime, {
index = 2,
target = {currentBlur = 0},
easing = "outQuint"
})
end
function PANEL:ShowBackground()
self:CreateAnimation(animationTime, {
index = 2,
target = {currentBlur = 1},
easing = "outQuint"
})
end
function PANEL:GetStandardSubpanelSize()
return ScrW() * 0.75 - self:GetPadding() * 3, ScrH() - self:GetPadding() * 2
end
function PANEL:SetupTab(name, info, sectionParent)
local bTable = istable(info)
local buttonColor = (bTable and info.buttonColor) or (ix.config.Get("color") or Color(140, 140, 140, 255))
local bDefault = (bTable and info.bDefault) or false
local qualifiedName = sectionParent and (sectionParent.name .. "/" .. name) or name
-- setup subpanels without populating them so we can retain the order
local subpanel = self:AddSubpanel(qualifiedName, true)
local id = subpanel.subpanelID
subpanel.info = info
subpanel.sectionParent = sectionParent and qualifiedName
subpanel:SetPaintedManually(true)
subpanel:SetTitle(nil)
if (sectionParent) then
-- hide section subpanels if they haven't been populated to seeing more subpanels than necessary
-- fly by as you navigate tabs in the menu
subpanel:SetSize(0, 0)
else
subpanel:SetSize(self:GetStandardSubpanelSize())
-- this is called while the subpanel has not been populated
subpanel.Paint = function(panel, width, height)
derma.SkinFunc("PaintPlaceholderPanel", panel, width, height)
end
end
local button
if (sectionParent) then
button = sectionParent:AddSection(L(name))
name = qualifiedName
else
button = self.tabs:Add("ixMenuSelectionButton")
button:SetText(L(name))
button:SizeToContents()
button:Dock(TOP)
button:SetButtonList(self.tabs.buttons)
button:SetBackgroundColor(buttonColor)
end
button.name = name
button.id = id
button.OnSelected = function()
self:TransitionSubpanel(id)
end
if (bTable and info.PopulateTabButton) then
info:PopulateTabButton(button)
end
-- don't allow sections in sections
if (sectionParent or !bTable or !info.Sections) then
return bDefault, button, subpanel
end
-- create button sections
for sectionName, sectionInfo in pairs(info.Sections) do
self:SetupTab(sectionName, sectionInfo, button)
end
return bDefault, button, subpanel
end
function PANEL:PopulateTabs()
local default
local tabs = {}
hook.Run("CreateMenuButtons", tabs)
for name, info in SortedPairs(tabs) do
local bDefault, button = self:SetupTab(name, info)
if (bDefault) then
default = button
end
end
if (ix.gui.lastMenuTab) then
for i = 1, #self.tabs.buttons do
local button = self.tabs.buttons[i]
if (button.id == ix.gui.lastMenuTab) then
default = button
break
end
end
end
if (!IsValid(default) and #self.tabs.buttons > 0) then
default = self.tabs.buttons[1]
end
if (IsValid(default)) then
default:SetSelected(true)
self:SetActiveSubpanel(default.id, 0)
end
self.buttons:MoveToFront()
self.guard:MoveToBefore(self.buttons)
end
function PANEL:AddManuallyPaintedChild(panel)
panel:SetParent(self)
panel:SetPaintedManually(panel)
self.manualChildren[#self.manualChildren + 1] = panel
end
function PANEL:OnKeyCodePressed(key)
self.noAnchor = CurTime() + 0.5
if (key == KEY_TAB) then
self:Remove()
end
end
function PANEL:Think()
if (IsValid(self.projectedTexture)) then
local forward = LocalPlayer():GetForward()
forward.z = 0
local right = LocalPlayer():GetRight()
right.z = 0
self.projectedTexture:SetBrightness(self.overviewFraction * 4)
self.projectedTexture:SetPos(LocalPlayer():GetPos() + right * 16 - forward * 8 + self.projectedTexturePosition)
self.projectedTexture:SetAngles(forward:Angle() + self.projectedTextureRotation)
self.projectedTexture:Update()
end
if (self.bClosing) then
return
end
local bTabDown = input.IsKeyDown(KEY_TAB)
if (bTabDown and (self.noAnchor or CurTime() + 0.4) < CurTime() and self.anchorMode) then
self.anchorMode = false
surface.PlaySound("buttons/lightswitch2.wav")
end
if ((!self.anchorMode and !bTabDown) or gui.IsGameUIVisible()) then
self:Remove()
if (ix.option.Get("escCloseMenu", false)) then
gui.HideGameUI()
end
end
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintMenuBackground", self, width, height, self.currentBlur)
local bShouldScale = self.currentAlpha != 255
if (bShouldScale) then
local currentScale = Lerp(self.currentAlpha / 255, 0.9, 1)
local matrix = Matrix()
matrix:Scale(matrixZScale * currentScale)
matrix:Translate(Vector(
ScrW() * 0.5 - (ScrW() * currentScale * 0.5),
ScrH() * 0.5 - (ScrH() * currentScale * 0.5),
1
))
cam.PushModelMatrix(matrix)
end
BaseClass.Paint(self, width, height)
self:PaintSubpanels(width, height)
self.buttons:PaintManual()
for i = 1, #self.manualChildren do
self.manualChildren[i]:PaintManual()
end
if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then
for i = 1, #ix.gui.inv1.childPanels do
local panel = ix.gui.inv1.childPanels[i]
if (IsValid(panel)) then
panel:PaintManual()
end
end
end
if (bShouldScale) then
cam.PopModelMatrix()
end
end
function PANEL:PerformLayout()
self.guard:SetSize(self.tabs:GetWide() + self:GetPadding() * 2, self:GetTall())
end
function PANEL:Remove()
self.bClosing = true
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
self:SetCharacterOverview(false, animationTime * 0.5)
-- remove input from opened child panels since they grab focus
if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then
for i = 1, #ix.gui.inv1.childPanels do
local panel = ix.gui.inv1.childPanels[i]
if (IsValid(panel)) then
panel:SetMouseInputEnabled(false)
panel:SetKeyboardInputEnabled(false)
end
end
end
CloseDermaMenus()
gui.EnableScreenClicker(false)
self:CreateAnimation(animationTime * 0.5, {
index = 2,
target = {currentBlur = 0},
easing = "outQuint"
})
self:CreateAnimation(animationTime * 0.5, {
target = {currentAlpha = 0},
easing = "outQuint",
-- we don't animate the blur because blurring doesn't draw things
-- with amount < 1 very well, resulting in jarring transition
Think = function(animation, panel)
panel:SetAlpha(panel.currentAlpha)
end,
OnComplete = function(animation, panel)
if (IsValid(panel.projectedTexture)) then
panel.projectedTexture:Remove()
end
BaseClass.Remove(panel)
end
})
end
vgui.Register("ixMenu", PANEL, "ixSubpanelParent")
if (IsValid(ix.gui.menu)) then
ix.gui.menu:Remove()
end
ix.gui.lastMenuTab = nil
================================================
FILE: gamemode/core/derma/cl_menubutton.lua
================================================
local buttonPadding = ScreenScale(14) * 0.5
local animationTime = 0.5
-- base menu button
DEFINE_BASECLASS("DButton")
local PANEL = {}
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha")
function PANEL:Init()
self:SetFont("ixMenuButtonFont")
self:SetTextColor(color_white)
self:SetPaintBackground(false)
self:SetContentAlignment(4)
self:SetTextInset(buttonPadding, 0)
self.padding = {32, 12, 32, 12} -- left, top, right, bottom
self.backgroundColor = Color(0, 0, 0)
self.backgroundAlpha = 128
self.currentBackgroundAlpha = 0
end
function PANEL:GetPadding()
return self.padding
end
function PANEL:SetPadding(left, top, right, bottom)
self.padding = {
left or self.padding[1],
top or self.padding[2],
right or self.padding[3],
bottom or self.padding[4]
}
end
function PANEL:SetText(text, noTranslation)
BaseClass.SetText(self, noTranslation and text:utf8upper() or L(text):utf8upper())
end
function PANEL:SizeToContents()
BaseClass.SizeToContents(self)
local width, height = self:GetSize()
self:SetSize(width + self.padding[1] + self.padding[3], height + self.padding[2] + self.padding[4])
end
function PANEL:PaintBackground(width, height)
surface.SetDrawColor(ColorAlpha(self.backgroundColor, self.currentBackgroundAlpha))
surface.DrawRect(0, 0, width, height)
end
function PANEL:Paint(width, height)
self:PaintBackground(width, height)
BaseClass.Paint(self, width, height)
end
function PANEL:SetTextColorInternal(color)
BaseClass.SetTextColor(self, color)
self:SetFGColor(color)
end
function PANEL:SetTextColor(color)
self:SetTextColorInternal(color)
self.color = color
end
function PANEL:SetDisabled(bValue)
local color = self.color
if (bValue) then
self:SetTextColorInternal(Color(math.max(color.r - 60, 0), math.max(color.g - 60, 0), math.max(color.b - 60, 0)))
else
self:SetTextColorInternal(color)
end
BaseClass.SetDisabled(self, bValue)
end
function PANEL:OnCursorEntered()
if (self:GetDisabled()) then
return
end
local color = self:GetTextColor()
self:SetTextColorInternal(Color(math.max(color.r - 25, 0), math.max(color.g - 25, 0), math.max(color.b - 25, 0)))
self:CreateAnimation(0.15, {
target = {currentBackgroundAlpha = self.backgroundAlpha}
})
LocalPlayer():EmitSound("Helix.Rollover")
end
function PANEL:OnCursorExited()
if (self:GetDisabled()) then
return
end
if (self.color) then
self:SetTextColor(self.color)
else
self:SetTextColor(color_white)
end
self:CreateAnimation(0.15, {
target = {currentBackgroundAlpha = 0}
})
end
function PANEL:OnMousePressed(code)
if (self:GetDisabled()) then
return
end
if (self.color) then
self:SetTextColor(self.color)
else
self:SetTextColor(ix.config.Get("color"))
end
LocalPlayer():EmitSound("Helix.Press")
if (code == MOUSE_LEFT and self.DoClick) then
self:DoClick(self)
elseif (code == MOUSE_RIGHT and self.DoRightClick) then
self:DoRightClick(self)
end
end
function PANEL:OnMouseReleased(key)
if (self:GetDisabled()) then
return
end
if (self.color) then
self:SetTextColor(self.color)
else
self:SetTextColor(color_white)
end
end
vgui.Register("ixMenuButton", PANEL, "DButton")
-- selection menu button
DEFINE_BASECLASS("ixMenuButton")
PANEL = {}
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
AccessorFunc(PANEL, "selected", "Selected", FORCE_BOOL)
AccessorFunc(PANEL, "buttonList", "ButtonList")
function PANEL:Init()
self.backgroundColor = color_white
self.selected = false
self.buttonList = {}
self.sectionPanel = nil -- sub-sections this button has; created only if it has any sections
end
function PANEL:PaintBackground(width, height)
local alpha = self.selected and 255 or self.currentBackgroundAlpha
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ColorAlpha(self.backgroundColor, alpha))
end
function PANEL:SetSelected(bValue, bSelectedSection)
self.selected = bValue
if (bValue) then
self:OnSelected()
if (self.sectionPanel) then
self.sectionPanel:Show()
elseif (self.sectionParent) then
self.sectionParent.sectionPanel:Show()
end
elseif (self.sectionPanel and self.sectionPanel:IsVisible() and !bSelectedSection) then
self.sectionPanel:Hide()
end
end
function PANEL:SetButtonList(list, bNoAdd)
if (!bNoAdd) then
list[#list + 1] = self
end
self.buttonList = list
end
function PANEL:GetSectionPanel()
return self.sectionPanel
end
function PANEL:AddSection(name)
if (!IsValid(self.sectionPanel)) then
-- add section panel to regular button list
self.sectionPanel = vgui.Create("ixMenuSelectionList", self:GetParent())
self.sectionPanel:Dock(self:GetDock())
self.sectionPanel:SetParentButton(self)
end
return self.sectionPanel:AddButton(name, self.buttonList)
end
function PANEL:OnMousePressed(key)
for _, v in pairs(self.buttonList) do
if (IsValid(v) and v != self) then
v:SetSelected(false, self.sectionParent == v)
end
end
self:SetSelected(true)
BaseClass.OnMousePressed(self, key)
end
function PANEL:OnSelected()
end
vgui.Register("ixMenuSelectionButton", PANEL, "ixMenuButton")
-- collapsable list for menu button sections
PANEL = {}
AccessorFunc(PANEL, "parent", "ParentButton")
function PANEL:Init()
self.parent = nil -- button that is responsible for controlling this list
self.height = 0
self.targetHeight = 0
self:DockPadding(0, 1, 0, 1)
self:SetVisible(false)
self:SetTall(0)
end
function PANEL:AddButton(name, buttonList)
assert(IsValid(self.parent), "attempted to add button to ixMenuSelectionList without a ParentButton")
assert(buttonList ~= nil, "attempted to add button to ixMenuSelectionList without a buttonList")
local button = self:Add("ixMenuSelectionButton")
button.sectionParent = self.parent
button:SetTextInset(buttonPadding * 2, 0)
button:SetPadding(nil, 8, nil, 8)
button:SetFont("ixMenuButtonFontSmall")
button:Dock(TOP)
button:SetText(name)
button:SizeToContents()
button:SetButtonList(buttonList)
button:SetBackgroundColor(self.parent:GetBackgroundColor())
self.targetHeight = self.targetHeight + button:GetTall()
return button
end
function PANEL:Show()
self:SetVisible(true)
self:CreateAnimation(animationTime, {
index = 1,
target = {
height = self.targetHeight + 2 -- +2 for padding
},
easing = "outQuart",
Think = function(animation, panel)
panel:SetTall(panel.height)
end
})
end
function PANEL:Hide()
self:CreateAnimation(animationTime, {
index = 1,
target = {
height = 0
},
easing = "outQuint",
Think = function(animation, panel)
panel:SetTall(panel.height)
end,
OnComplete = function(animation, panel)
panel:SetVisible(false)
end
})
end
function PANEL:Paint(width, height)
surface.SetDrawColor(Color(255, 255, 255, 33))
surface.DrawRect(0, 0, width, 1)
surface.DrawRect(0, height - 1, width, 1)
end
vgui.Register("ixMenuSelectionList", PANEL, "Panel")
================================================
FILE: gamemode/core/derma/cl_modelpanel.lua
================================================
DEFINE_BASECLASS("DModelPanel")
local PANEL = {}
local MODEL_ANGLE = Angle(0, 45, 0)
function PANEL:Init()
self.brightness = 1
self:SetCursor("none")
end
function PANEL:SetModel(model, skin, bodygroups)
if (IsValid(self.Entity)) then
self.Entity:Remove()
self.Entity = nil
end
if (!ClientsideModel) then
return
end
local entity = ClientsideModel(model, RENDERGROUP_OPAQUE)
if (!IsValid(entity)) then
return
end
entity:SetNoDraw(true)
entity:SetIK(false)
if (skin) then
entity:SetSkin(skin)
end
if (isstring(bodygroups)) then
entity:SetBodyGroups(bodygroups)
end
local sequence = entity:LookupSequence("idle_unarmed")
if (sequence <= 0) then
sequence = entity:SelectWeightedSequence(ACT_IDLE)
end
if (sequence > 0) then
entity:ResetSequence(sequence)
else
local found = false
for _, v in ipairs(entity:GetSequenceList()) do
if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then
entity:ResetSequence(v)
found = true
break
end
end
if (!found) then
entity:ResetSequence(4)
end
end
self.Entity = entity
end
function PANEL:LayoutEntity()
local scrW, scrH = ScrW(), ScrH()
local xRatio = gui.MouseX() / scrW
local yRatio = gui.MouseY() / scrH
local x, _ = self:LocalToScreen(self:GetWide() / 2)
local xRatio2 = x / scrW
local entity = self.Entity
entity:SetPoseParameter("head_pitch", yRatio*90 - 30)
entity:SetPoseParameter("head_yaw", (xRatio - xRatio2)*90 - 5)
entity:SetAngles(MODEL_ANGLE)
entity:SetIK(false)
if (self.copyLocalSequence) then
entity:SetSequence(LocalPlayer():GetSequence())
entity:SetPoseParameter("move_yaw", 360 * LocalPlayer():GetPoseParameter("move_yaw") - 180)
end
self:RunAnimation()
end
function PANEL:DrawModel()
local brightness = self.brightness * 0.4
local brightness2 = self.brightness * 1.5
render.SetStencilEnable(false)
render.SetColorMaterial()
render.SetColorModulation(1, 1, 1)
render.SetModelLighting(0, brightness2, brightness2, brightness2)
for i = 1, 4 do
render.SetModelLighting(i, brightness, brightness, brightness)
end
local fraction = (brightness / 1) * 0.1
render.SetModelLighting(5, fraction, fraction, fraction)
-- Excecute Some stuffs
if (self.enableHook) then
hook.Run("DrawHelixModelView", self, self.Entity)
end
self.Entity:DrawModel()
if (self.enableHook) then
hook.Run("PostDrawHelixModelView", self, self.Entity)
end
end
function PANEL:OnMousePressed()
end
vgui.Register("ixModelPanel", PANEL, "DModelPanel")
================================================
FILE: gamemode/core/derma/cl_notice.lua
================================================
local animationTime = 0.75
-- notice manager
-- this manages positions/animations for notice panels
local PANEL = {}
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
function PANEL:Init()
self:SetSize(ScrW() * 0.4, ScrH())
self:SetPos(ScrW() - ScrW() * 0.4, 0)
self:SetZPos(-99999)
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
self.notices = {}
self.padding = 4
end
function PANEL:GetAll()
return self.notices
end
function PANEL:Clear()
for _, v in ipairs(self.notices) do
self:RemoveNotice(v)
end
end
function PANEL:AddNotice(text, bError)
if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu.bClosing) then
return
end
local textLength = text:utf8len()
local panel = self:Add("ixNotice")
panel:SetText(text)
panel:SetError(bError or text:utf8sub(textLength, textLength) == "!")
panel:SizeToContents()
panel.currentY = -panel:GetTall()
panel:SetPos(self.padding, panel.currentY)
-- setup duration timer
panel:CreateAnimation(ix.option.Get("noticeDuration", 8), {
index = 2,
target = {duration = 1},
bIgnoreConfig = true,
OnComplete = function(animation, this)
self:RemoveNotice(this)
end
})
table.insert(self.notices, 1, panel)
self:Organize()
-- remove old notice if we've hit the limit of notices
if (#self.notices > ix.option.Get("noticeMax", 4)) then
for i = #self.notices, 1, -1 do
local notice = self.notices[i]
if (IsValid(notice) and !notice.bClosing) then
self:RemoveNotice(notice)
break
end
end
end
return panel
end
function PANEL:RemoveNotice(panel)
panel.bClosing = true
panel:CreateAnimation(animationTime, {
index = 3,
target = {outAnimation = 0},
easing = "outQuint",
OnComplete = function(animation, this)
local toRemove
for k, v in ipairs(self.notices) do
if (v == this) then
toRemove = k
break
end
end
if (toRemove) then
table.remove(self.notices, toRemove)
end
this:SetText("") -- (hack) text remains for a frame after remove is called, so let's make sure we don't draw it
this:Remove()
end
})
end
-- update target Y positions and animations
function PANEL:Organize()
local currentTarget = self.padding
for _, v in ipairs(self.notices) do
v:CreateAnimation(animationTime, {
index = 1,
target = {currentY = currentTarget},
easing = "outElastic",
Think = function(animation, panel)
panel:SetPos(
self:GetWide() - panel:GetWide() - self.padding,
math.min(panel.currentY + 1, currentTarget) -- easing eventually hits subpixel movement so we level it off
)
end
})
currentTarget = currentTarget + self.padding + v:GetTall()
end
end
vgui.Register("ixNoticeManager", PANEL, "Panel")
-- notice panel
-- these do not manage their own enter/exit animations or lifetime
DEFINE_BASECLASS("DLabel")
PANEL = {}
AccessorFunc(PANEL, "bError", "Error", FORCE_BOOL)
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
function PANEL:Init()
self:SetSize(256, 36)
self:SetContentAlignment(5)
self:SetExpensiveShadow(1, Color(0, 0, 0, 150))
self:SetFont("ixNoticeFont")
self:SetTextColor(color_white)
self:SetDrawOnTop(true)
self:DockPadding(0, 0, 0, 0)
self:DockMargin(0, 0, 0, 0)
self.bError = false
self.bHovered = false
self.errorAnimation = 0
self.padding = 8
self.currentY = 0
self.duration = 0
self.outAnimation = 1
self.alpha = 255
LocalPlayer():EmitSound("Helix.Notify")
end
function PANEL:SetError(bValue)
self.bError = tobool(bValue)
if (bValue) then
self.errorAnimation = 1
self:CreateAnimation(animationTime, {
index = 5,
target = {errorAnimation = 0},
easing = "outQuint"
})
end
end
function PANEL:SizeToContents()
local contentWidth, contentHeight = self:GetContentSize()
contentWidth = contentWidth + self.padding * 2
contentHeight = contentHeight + self.padding * 2
local manager = ix.gui.notices
local maxWidth = math.min(IsValid(manager) and (manager:GetWide() - manager:GetPadding() * 2) or ScrW(), contentWidth)
if (contentWidth > maxWidth) then
self:SetWide(maxWidth)
self:SetTextInset(self.padding * 2, 0)
self:SetWrap(true)
self:SizeToContentsY()
self:SetWide(self:GetContentSize())
else
self:SetSize(contentWidth, contentHeight)
end
end
function PANEL:SizeToContentsY()
BaseClass.SizeToContentsY(self)
self:SetTall(self:GetTall() + self.padding * 2)
end
function PANEL:OnMouseHover()
self:CreateAnimation(animationTime * 0.5, {
index = 4,
target = {alpha = 0},
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(panel.alpha)
end
})
end
function PANEL:OnMouseLeave()
self:CreateAnimation(animationTime * 0.5, {
index = 4,
target = {alpha = 255},
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(panel.alpha)
end
})
end
function PANEL:Paint(width, height)
if (self.outAnimation < 1) then
local x, y = self:LocalToScreen(0, 0)
render.SetScissorRect(x, y, x + self:GetWide(), y + (self:GetTall() * self.outAnimation), true)
end
local x, y = self:LocalToScreen(0, 0)
local mouseX, mouseY = gui.MousePos()
if (mouseX >= x and mouseX <= x + width and
mouseY >= y and mouseY <= y + height) then
if (!self.bHovered) then
self.bHovered = true
self:OnMouseHover()
end
elseif (self.bHovered) then
self.bHovered = false
self:OnMouseLeave()
end
ix.util.DrawBlur(self)
if (self.errorAnimation > 0) then
local color = derma.GetColor("Error", self)
surface.SetDrawColor(
color.r * self.errorAnimation,
color.g * self.errorAnimation,
color.b * self.errorAnimation,
self.errorAnimation * 255 + ((1 - self.errorAnimation) * 66)
)
else
surface.SetDrawColor(0, 0, 0, 66)
end
surface.DrawRect(0, 0, width, height)
surface.SetDrawColor(self.bError and derma.GetColor("Error", self) or ix.config.Get("color"))
surface.DrawRect(0, height - 1, width * self.duration, 1)
end
function PANEL:PaintOver(width, height)
render.SetScissorRect(0, 0, 0, 0, false)
end
vgui.Register("ixNotice", PANEL, "DLabel")
if (IsValid(ix.gui.notices)) then
ix.gui.notices:Remove()
ix.gui.notices = vgui.Create("ixNoticeManager")
else
ix.gui.notices = vgui.Create("ixNoticeManager")
end
================================================
FILE: gamemode/core/derma/cl_noticebar.lua
================================================
local PANEL = {
types = {
"Info", -- info
"Success", -- success
"Error" -- error
}
}
AccessorFunc(PANEL, "type", "Type", FORCE_NUMBER)
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
AccessorFunc(PANEL, "length", "Length", FORCE_NUMBER)
AccessorFunc(PANEL, "hidden", "Hidden", FORCE_BOOL)
function PANEL:Init()
self.type = 1
self.padding = 8
self.length = 4
self.currentY = 0
self.hidden = true
self.text = self:Add("DLabel")
self.text:SetFont("ixNoticeFont")
self.text:SetContentAlignment(5)
self.text:SetTextColor(color_white)
self.text:SizeToContents()
self.text:Dock(FILL)
self:SetSize(self:GetParent():GetWide() - (self.padding * 4), self.text:GetTall() + (self.padding * 2))
self:SetPos(self.padding * 2, -self:GetTall() - self.padding)
end
function PANEL:SetFont(value)
self.text:SetFont(value)
self.text:SizeToContents()
end
function PANEL:SetText(text)
self.text:SetText(text)
self.text:SizeToContents()
end
function PANEL:Slide(direction, length)
direction = direction or "up"
length = length or 0.5
timer.Remove("ixNoticeBarAnimation")
local x, _ = self:GetPos()
local baseY = direction == "up" and self.padding * 2 or (-self:GetTall() - self.padding)
local targetY = direction == "up" and (-self:GetTall() - self.padding) or self.padding * 2
local easing = direction == "up" and "outQuint" or "outElastic"
self:SetPos(x, baseY)
self.currentY = baseY
self.hidden = direction == "up"
self:CreateAnimation(length, {
target = {currentY = targetY},
easing = easing,
Think = function(animation, panel)
local lastX, _ = panel:GetPos()
panel:SetPos(lastX, panel.currentY)
end
})
end
function PANEL:Show(bRemove)
self:Slide("down")
timer.Create("ixNoticeBarAnimation", self.length - 0.5, 1, function()
if (!IsValid(self)) then
return
end
self:Slide("up")
end)
end
function PANEL:Paint(width, height)
local color = derma.GetColor(self.types[self.type], self)
surface.SetDrawColor(color)
surface.DrawRect(0, 0, width, height)
end
vgui.Register("ixNoticeBar", PANEL, "Panel")
================================================
FILE: gamemode/core/derma/cl_overrides.lua
================================================
-- overrides standard derma panels to add/change functionality
local PANEL = {}
local OVERRIDES = {}
-- @todo remove me when autorefresh support is no longer needed
local function OverridePanel(name, func)
PANEL = vgui.GetControlTable(name)
if (!istable(PANEL)) then
return
end
OVERRIDES = {}
func()
for k, _ in pairs(PANEL) do
local overrideName = "ix" .. k
if (PANEL[overrideName] and !OVERRIDES[k]) then
print("unhooking override ", overrideName)
PANEL[k] = PANEL[overrideName]
PANEL[overrideName] = nil
end
end
end
local function Override(name)
local oldMethod = "ix" .. name
OVERRIDES[name] = true
if (PANEL[oldMethod]) then
return
end
PANEL[oldMethod] = PANEL[name]
end
OverridePanel("DMenuOption", function()
function PANEL:PerformLayout()
self:SizeToContents()
self:SetWide(self:GetWide() + 30)
local w = math.max(self:GetParent():GetWide(), self:GetWide())
self:SetSize(w, self:GetTall() + 4)
if (self.SubMenuArrow) then
self.SubMenuArrow:SetSize(15, 15)
self.SubMenuArrow:CenterVertical()
self.SubMenuArrow:AlignRight(4)
end
DButton.PerformLayout(self)
end
end)
OverridePanel("DMenu", function()
local animationTime = 0.33
Override("Init")
function PANEL:Init(...)
self:ixInit(...)
self.ixAnimation = 0
end
function PANEL:SetFont(font)
for _, v in pairs(self:GetCanvas():GetChildren()) do
v:SetFont(font)
v:SizeToContents()
end
-- reposition for the new font
self:InvalidateLayout(true)
self:Open(self.ixX, self.ixY, false, self.ixOwnerPanel)
end
Override("SetSize")
function PANEL:SetSize(width, height)
self:ixSetSize(width, height)
self.ixTargetHeight = height
end
Override("PerformLayout")
function PANEL:PerformLayout(...)
self:ixPerformLayout(...)
if (self.ixAnimating) then
self.VBar:SetAlpha(0) -- setvisible doesn't seem to work here
self:SetTall(self.ixAnimation * self.ixTargetHeight)
else
self.VBar:SetAlpha(255)
end
end
Override("OnMouseWheeled")
function PANEL:OnMouseWheeled(delta)
self:ixOnMouseWheeled(delta)
-- don't allow the input event to fall through
return true
end
Override("AddOption")
function PANEL:AddOption(...)
local panel = self:ixAddOption(...)
panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black))
panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes
return panel
end
Override("AddSubMenu")
function PANEL:AddSubMenu(...)
local menu, panel = self:ixAddSubMenu(...)
panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black))
panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes
return menu, panel
end
Override("Open")
function PANEL:Open(x, y, bSkipAnimation, ownerPanel)
self.ixX, self.ixY, self.ixOwnerPanel = x, y, ownerPanel
self:ixOpen(x, y, bSkipAnimation, ownerPanel)
if (ix.option.Get("disableAnimations")) then
return
end
-- remove pac3 derma menu hooks since animations don't play nicely
hook.Remove("CloseDermaMenus", self)
hook.Remove("Think", self)
self.ixAnimating = true
self:CreateAnimation(animationTime, {
index = 1,
target = {ixAnimation = 1},
easing = "outQuint",
Think = function(animation, panel)
panel:InvalidateLayout(true)
end,
OnComplete = function(animation, panel)
panel.ixAnimating = nil
end
})
end
Override("Hide")
function PANEL:Hide()
if (ix.option.Get("disableAnimations")) then
self:ixHide()
return
end
self.ixAnimating = true
self:SetVisible(true)
self:CreateAnimation(animationTime * 0.5, {
index = 1,
target = {ixAnimation = 0},
easing = "outQuint",
Think = function(animation, panel)
panel:InvalidateLayout(true)
end,
OnComplete = function(animation, panel)
panel.ixAnimating = false
panel:ixHide()
end
})
end
Override("Remove")
function PANEL:Remove()
if (self.ixRemoving) then
return
end
if (ix.option.Get("disableAnimations")) then
self:ixRemove()
return
end
self.ixAnimating = true
self.ixRemoving = true
self:SetVisible(true)
self:CreateAnimation(animationTime * 0.5, {
index = 1,
target = {ixAnimation = 0},
easing = "outQuint",
Think = function(animation, panel)
panel:InvalidateLayout(true)
end,
OnComplete = function(animation, panel)
panel:ixRemove()
end
})
end
end)
OverridePanel("DComboBox", function()
Override("OpenMenu")
function PANEL:OpenMenu()
self:ixOpenMenu()
if (IsValid(self.Menu)) then
local _, y = self.Menu:LocalToScreen(self.Menu:GetPos())
self.Menu:SetFont(self:GetFont())
self.Menu:SetMaxHeight(ScrH() - y)
end
end
end)
OverridePanel("DScrollPanel", function()
Override("ScrollToChild")
function PANEL:ScrollToChild(panel)
-- docked panels required InvalidateParent in order to retrieve their position correctly
if (panel:GetDock() != NODOCK) then
panel:InvalidateParent(true)
else
self:PerformLayout()
end
local _, y = self.pnlCanvas:GetChildPosition(panel)
y = y + panel:GetTall() * 0.5
y = y - self:GetTall() * 0.5
self.VBar:SetScroll(y)
end
end)
================================================
FILE: gamemode/core/derma/cl_scoreboard.lua
================================================
local rowPaintFunctions = {
function(width, height)
end,
function(width, height)
surface.SetDrawColor(30, 30, 30, 25)
surface.DrawRect(0, 0, width, height)
end
}
-- character icon
-- we can't customize the rendering of ModelImage so we have to do it ourselves
local PANEL = {}
local BODYGROUPS_EMPTY = "000000000"
AccessorFunc(PANEL, "model", "Model", FORCE_STRING)
AccessorFunc(PANEL, "bHidden", "Hidden", FORCE_BOOL)
function PANEL:Init()
self:SetSize(64, 64)
self.bodygroups = BODYGROUPS_EMPTY
end
function PANEL:SetModel(model, skin, bodygroups)
model = model:gsub("\\", "/")
if (isstring(bodygroups)) then
if (bodygroups:len() == 9) then
for i = 1, bodygroups:len() do
self:SetBodygroup(i, tonumber(bodygroups[i]) or 0)
end
else
self.bodygroups = BODYGROUPS_EMPTY
end
end
self.model = model
self.skin = skin
self.path = "materials/spawnicons/" ..
model:sub(1, #model - 4) .. -- remove extension
((isnumber(skin) and skin > 0) and ("_skin" .. tostring(skin)) or "") .. -- skin number
(self.bodygroups != BODYGROUPS_EMPTY and ("_" .. self.bodygroups) or "") .. -- bodygroups
".png"
local material = Material(self.path, "smooth")
-- we don't have a cached spawnicon texture, so we need to forcefully generate one
if (material:IsError()) then
self.id = "ixScoreboardIcon" .. self.path
self.renderer = self:Add("ModelImage")
self.renderer:SetVisible(false)
self.renderer:SetModel(model, skin, self.bodygroups)
self.renderer:RebuildSpawnIcon()
-- this is the only way to get a callback for generated spawn icons, it's bad but it's only done once
hook.Add("SpawniconGenerated", self.id, function(lastModel, filePath, modelsLeft)
filePath = filePath:gsub("\\", "/"):lower()
if (filePath == self.path) then
hook.Remove("SpawniconGenerated", self.id)
self.material = Material(filePath, "smooth")
self.renderer:Remove()
end
end)
else
self.material = material
end
end
function PANEL:SetBodygroup(k, v)
if (k < 0 or k > 8 or v < 0 or v > 9) then
return
end
self.bodygroups = self.bodygroups:SetChar(k + 1, v)
end
function PANEL:GetModel()
return self.model or "models/error.mdl"
end
function PANEL:GetSkin()
return self.skin or 1
end
function PANEL:DoClick()
end
function PANEL:DoRightClick()
end
function PANEL:OnMouseReleased(key)
if (key == MOUSE_LEFT) then
self:DoClick()
elseif (key == MOUSE_RIGHT) then
self:DoRightClick()
end
end
function PANEL:Paint(width, height)
if (!self.material) then
return
end
surface.SetMaterial(self.material)
surface.SetDrawColor(self.bHidden and color_black or color_white)
surface.DrawTexturedRect(0, 0, width, height)
end
function PANEL:Remove()
if (self.id) then
hook.Remove("SpawniconGenerated", self.id)
end
end
vgui.Register("ixScoreboardIcon", PANEL, "Panel")
-- player row
PANEL = {}
AccessorFunc(PANEL, "paintFunction", "BackgroundPaintFunction")
function PANEL:Init()
self:SetTall(64)
self.icon = self:Add("ixScoreboardIcon")
self.icon:Dock(LEFT)
self.icon.DoRightClick = function()
local client = self.player
if (!IsValid(client)) then
return
end
local menu = DermaMenu()
menu:AddOption(L("viewProfile"), function()
client:ShowProfile()
end)
menu:AddOption(L("copySteamID"), function()
SetClipboardText(client:IsBot() and client:EntIndex() or client:SteamID())
end)
hook.Run("PopulateScoreboardPlayerMenu", client, menu)
menu:Open()
end
self.icon:SetHelixTooltip(function(tooltip)
local client = self.player
if (IsValid(self) and IsValid(client)) then
ix.hud.PopulatePlayerTooltip(tooltip, client)
end
end)
self.name = self:Add("DLabel")
self.name:DockMargin(4, 4, 0, 0)
self.name:Dock(TOP)
self.name:SetTextColor(color_white)
self.name:SetFont("ixGenericFont")
self.description = self:Add("DLabel")
self.description:DockMargin(5, 0, 0, 0)
self.description:Dock(TOP)
self.description:SetTextColor(color_white)
self.description:SetFont("ixSmallFont")
self.paintFunction = rowPaintFunctions[1]
self.nextThink = CurTime() + 1
end
function PANEL:Update()
local client = self.player
local model = client:GetModel()
local skin = client:GetSkin()
local name = client:GetName()
local description = hook.Run("GetCharacterDescription", client) or
(client:GetCharacter() and client:GetCharacter():GetDescription()) or ""
local bRecognize = false
local localCharacter = LocalPlayer():GetCharacter()
local character = IsValid(self.player) and self.player:GetCharacter()
if (localCharacter and character) then
bRecognize = hook.Run("IsCharacterRecognized", localCharacter, character:GetID())
or hook.Run("IsPlayerRecognized", self.player)
end
self.icon:SetHidden(!bRecognize)
self:SetZPos(bRecognize and 1 or 2)
-- no easy way to check bodygroups so we'll just set them anyway
for _, v in pairs(client:GetBodyGroups()) do
self.icon:SetBodygroup(v.id, client:GetBodygroup(v.id))
end
if (self.icon:GetModel() != model or self.icon:GetSkin() != skin) then
self.icon:SetModel(model, skin)
self.icon:SetTooltip(nil)
end
if (self.name:GetText() != name) then
self.name:SetText(name)
self.name:SizeToContents()
end
if (self.description:GetText() != description) then
self.description:SetText(description)
self.description:SizeToContents()
end
end
function PANEL:Think()
if (CurTime() >= self.nextThink) then
local client = self.player
if (!IsValid(client) or !client:GetCharacter() or self.character != client:GetCharacter() or self.team != client:Team()) then
self:Remove()
self:GetParent():SizeToContents()
end
self.nextThink = CurTime() + 1
end
end
function PANEL:SetPlayer(client)
self.player = client
self.team = client:Team()
self.character = client:GetCharacter()
self:Update()
end
function PANEL:Paint(width, height)
self.paintFunction(width, height)
end
vgui.Register("ixScoreboardRow", PANEL, "EditablePanel")
-- faction grouping
PANEL = {}
AccessorFunc(PANEL, "faction", "Faction")
function PANEL:Init()
self:DockMargin(0, 0, 0, 16)
self:SetTall(32)
self.nextThink = 0
end
function PANEL:AddPlayer(client, index)
if (!IsValid(client) or !client:GetCharacter() or hook.Run("ShouldShowPlayerOnScoreboard", client) == false) then
return false
end
local id = index % 2 == 0 and 1 or 2
local panel = self:Add("ixScoreboardRow")
panel:SetPlayer(client)
panel:Dock(TOP)
panel:SetZPos(2)
panel:SetBackgroundPaintFunction(rowPaintFunctions[id])
self:SizeToContents()
client.ixScoreboardSlot = panel
return true
end
function PANEL:SetFaction(faction)
self:SetColor(faction.color)
self:SetText(L(faction.name))
self.faction = faction
end
function PANEL:Update()
local faction = self.faction
if (team.NumPlayers(faction.index) == 0) then
self:SetVisible(false)
self:GetParent():InvalidateLayout()
else
local bHasPlayers
for k, v in ipairs(team.GetPlayers(faction.index)) do
if (!IsValid(v.ixScoreboardSlot)) then
if (self:AddPlayer(v, k)) then
bHasPlayers = true
end
else
v.ixScoreboardSlot:Update()
bHasPlayers = true
end
end
self:SetVisible(bHasPlayers)
end
end
vgui.Register("ixScoreboardFaction", PANEL, "ixCategoryPanel")
-- main scoreboard panel
PANEL = {}
function PANEL:Init()
if (IsValid(ix.gui.scoreboard)) then
ix.gui.scoreboard:Remove()
end
self:Dock(FILL)
self.factions = {}
self.nextThink = 0
for i = 1, #ix.faction.indices do
local faction = ix.faction.indices[i]
local panel = self:Add("ixScoreboardFaction")
panel:SetFaction(faction)
panel:Dock(TOP)
self.factions[i] = panel
end
ix.gui.scoreboard = self
end
function PANEL:Think()
if (CurTime() >= self.nextThink) then
for i = 1, #self.factions do
local factionPanel = self.factions[i]
factionPanel:Update()
end
self.nextThink = CurTime() + 0.5
end
end
vgui.Register("ixScoreboard", PANEL, "DScrollPanel")
hook.Add("CreateMenuButtons", "ixScoreboard", function(tabs)
tabs["scoreboard"] = function(container)
container:Add("ixScoreboard")
end
end)
================================================
FILE: gamemode/core/derma/cl_settings.lua
================================================
local panelMap = {
[ix.type.bool] = "ixSettingsRowBool",
[ix.type.array] = "ixSettingsRowArray",
[ix.type.string] = "ixSettingsRowString",
[ix.type.number] = "ixSettingsRowNumber",
[ix.type.color] = "ixSettingsRowColor"
}
local function EmitChange(pitch)
LocalPlayer():EmitSound("weapons/ar2/ar2_empty.wav", 75, pitch or 150, 0.25)
end
-- color setting
local PANEL = {}
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
function PANEL:Init()
self.color = table.Copy(color_white)
self.padding = 4
self.panel = self:Add("Panel")
self.panel:SetCursor("hand")
self.panel:SetMouseInputEnabled(true)
self.panel:Dock(RIGHT)
self.panel.Paint = function(panel, width, height)
local padding = self.padding
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
surface.DrawRect(0, 0, width, height)
surface.SetDrawColor(self.color)
surface.DrawRect(padding, padding, width - padding * 2, height - padding * 2)
end
self.panel.OnMousePressed = function(panel, key)
if (key == MOUSE_LEFT) then
self:OpenPicker()
end
end
end
function PANEL:OpenPicker()
if (IsValid(self.picker)) then
self.picker:Remove()
return
end
self.picker = vgui.Create("ixSettingsRowColorPicker")
self.picker:Attach(self)
self.picker:SetValue(self.color)
self.picker.OnValueChanged = function(panel)
local newColor = panel:GetValue()
if (newColor != self.color) then
self.color = newColor
self:OnValueChanged(newColor)
end
end
self.picker.OnValueUpdated = function(panel)
self.color = panel:GetValue()
end
end
function PANEL:SetValue(value)
self.color = Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255)
end
function PANEL:GetValue()
return self.color
end
function PANEL:PerformLayout(width, height)
surface.SetFont("ixMenuButtonFont")
local totalWidth = surface.GetTextSize("999")
self.panel:SetSize(totalWidth + self.padding * 2, height)
end
vgui.Register("ixSettingsRowColor", PANEL, "ixSettingsRow")
-- color setting picker
DEFINE_BASECLASS("Panel")
PANEL = {}
AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL)
function PANEL:Init()
self.m_bIsMenuComponent = true
self.bDeleteSelf = true
self.realHeight = 200
self.height = 200
self:SetSize(250, 200)
self:DockPadding(4, 4, 4, 4)
self.picker = self:Add("DColorMixer")
self.picker:Dock(FILL)
self.picker.ValueChanged = function()
self:OnValueUpdated()
end
self:MakePopup()
RegisterDermaMenuForClose(self)
end
function PANEL:SetValue(value)
self.picker:SetColor(Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255))
end
function PANEL:GetValue()
return self.picker:GetColor()
end
function PANEL:Attach(panel)
self.attached = panel
end
function PANEL:Think()
local panel = self.attached
if (IsValid(panel)) then
local width, height = self:GetSize()
local x, y = panel:LocalToScreen(0, 0)
self:SetPos(
math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width),
math.Clamp(y + panel:GetTall(), 0, ScrH() - height)
)
end
end
function PANEL:Paint(width, height)
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
surface.DrawRect(0, 0, width, height)
end
function PANEL:OnValueChanged()
end
function PANEL:OnValueUpdated()
end
function PANEL:Remove()
if (self.bClosing) then
return
end
self:OnValueChanged()
-- @todo open/close animations
self.bClosing = true
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
BaseClass.Remove(self)
end
vgui.Register("ixSettingsRowColorPicker", PANEL, "EditablePanel")
-- number setting
PANEL = {}
function PANEL:Init()
self.setting = self:Add("ixNumSlider")
self.setting.nextUpdate = 0
self.setting:Dock(RIGHT)
self.setting.OnValueChanged = function(panel)
self:OnValueChanged(self:GetValue())
end
self.setting.OnValueUpdated = function(panel)
local fraction = panel:GetFraction()
if (fraction == 0) then
EmitChange(75)
return
elseif (fraction == 1) then
EmitChange(120)
return
end
if (SysTime() > panel.nextUpdate) then
EmitChange(85 + fraction * 15)
panel.nextUpdate = SysTime() + 0.05
end
end
local panel = self.setting:GetLabel()
panel:SetCursor("hand")
panel:SetMouseInputEnabled(true)
panel.OnMousePressed = function(_, key)
if (key == MOUSE_LEFT) then
self:OpenEntry()
end
end
end
function PANEL:OpenEntry()
if (IsValid(self.entry)) then
self.entry:Remove()
return
end
self.entry = vgui.Create("ixSettingsRowNumberEntry")
self.entry:Attach(self)
self.entry:SetValue(self:GetValue(), true)
self.entry.OnValueChanged = function(panel)
local value = math.Round(panel:GetValue(), self:GetDecimals())
if (value != self:GetValue()) then
self:SetValue(value, true)
self:OnValueChanged(value)
end
end
end
function PANEL:SetValue(value, bNoNotify)
self.setting:SetValue(value, bNoNotify)
end
function PANEL:GetValue()
return self.setting:GetValue()
end
function PANEL:SetMin(value)
self.setting:SetMin(value)
end
function PANEL:SetMax(value)
self.setting:SetMax(value)
end
function PANEL:SetDecimals(value)
self.setting:SetDecimals(value)
end
function PANEL:GetDecimals()
return self.setting:GetDecimals()
end
function PANEL:PerformLayout(width, height)
self.setting:SetWide(width * 0.5)
end
vgui.Register("ixSettingsRowNumber", PANEL, "ixSettingsRow")
-- number setting entry
DEFINE_BASECLASS("Panel")
PANEL = {}
AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL)
function PANEL:Init()
surface.SetFont("ixMenuButtonFont")
local width, height = surface.GetTextSize("999999")
self.m_bIsMenuComponent = true
self.bDeleteSelf = true
self.realHeight = 200
self.height = 200
self:SetSize(width, height)
self:DockPadding(4, 4, 4, 4)
self.textEntry = self:Add("ixTextEntry")
self.textEntry:SetNumeric(true)
self.textEntry:SetFont("ixMenuButtonFont")
self.textEntry:Dock(FILL)
self.textEntry:RequestFocus()
self.textEntry.OnEnter = function()
self:Remove()
end
self:MakePopup()
RegisterDermaMenuForClose(self)
end
function PANEL:SetValue(value, bInitial)
value = tostring(value)
self.textEntry:SetValue(value)
if (bInitial) then
self.textEntry:SetCaretPos(value:utf8len())
end
end
function PANEL:GetValue()
return tonumber(self.textEntry:GetValue()) or 0
end
function PANEL:Attach(panel)
self.attached = panel
end
function PANEL:Think()
local panel = self.attached
if (IsValid(panel)) then
local width, height = self:GetSize()
local x, y = panel:LocalToScreen(0, 0)
self:SetPos(
math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width),
math.Clamp(y + panel:GetTall(), 0, ScrH() - height)
)
end
end
function PANEL:Paint(width, height)
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
surface.DrawRect(0, 0, width, height)
end
function PANEL:OnValueChanged()
end
function PANEL:OnValueUpdated()
end
function PANEL:Remove()
if (self.bClosing) then
return
end
self:OnValueChanged()
-- @todo open/close animations
self.bClosing = true
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
BaseClass.Remove(self)
end
vgui.Register("ixSettingsRowNumberEntry", PANEL, "EditablePanel")
-- string setting
PANEL = {}
function PANEL:Init()
self.setting = self:Add("ixTextEntry")
self.setting:Dock(RIGHT)
self.setting:SetFont("ixMenuButtonFont")
self.setting:SetBackgroundColor(derma.GetColor("DarkerBackground", self))
self.setting.OnEnter = function()
self:OnValueChanged(self:GetValue())
end
end
function PANEL:SetValue(value)
self.setting:SetValue(tostring(value))
end
function PANEL:GetValue()
return self.setting:GetValue()
end
function PANEL:PerformLayout(width, height)
self.setting:SetWide(width * 0.5)
end
vgui.Register("ixSettingsRowString", PANEL, "ixSettingsRow")
-- bool setting
PANEL = {}
function PANEL:Init()
self.setting = self:Add("ixCheckBox")
self.setting:Dock(RIGHT)
self.setting.DoClick = function(panel)
self:OnValueChanged(self:GetValue())
end
end
function PANEL:SetValue(bValue)
bValue = tobool(bValue)
self.setting:SetChecked(bValue, true)
end
function PANEL:GetValue()
return self.setting:GetChecked()
end
vgui.Register("ixSettingsRowBool", PANEL, "ixSettingsRow")
-- array setting
PANEL = {}
function PANEL:Init()
self.array = {}
self.setting = self:Add("DComboBox")
self.setting:Dock(RIGHT)
self.setting:SetFont("ixMenuButtonFont")
self.setting:SetTextColor(color_white)
self.setting.OnSelect = function(panel)
self:OnValueChanged(self:GetValue())
panel:SizeToContents()
panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice)
if (!self.bInitial) then
EmitChange()
end
end
end
function PANEL:Populate(key, info)
if (!isfunction(info.populate)) then
ErrorNoHalt(string.format("expected populate function for array option '%s'", key))
return
end
local entries = info.populate()
local i = 1
for k, v in pairs(entries) do
self.setting:AddChoice(v, k)
self.array[k] = i
i = i + 1
end
end
function PANEL:SetValue(value)
self.bInitial = true
self.setting:ChooseOptionID(self.array[value])
self.bInitial = false
end
function PANEL:GetValue()
return select(2, self.setting:GetSelected())
end
vgui.Register("ixSettingsRowArray", PANEL, "ixSettingsRow")
-- settings row
PANEL = {}
AccessorFunc(PANEL, "backgroundIndex", "BackgroundIndex", FORCE_NUMBER)
AccessorFunc(PANEL, "bShowReset", "ShowReset", FORCE_BOOL)
function PANEL:Init()
self:DockPadding(4, 4, 4, 4)
self.text = self:Add("DLabel")
self.text:Dock(LEFT)
self.text:SetFont("ixMenuButtonFont")
self.text:SetExpensiveShadow(1, color_black)
self.backgroundIndex = 0
end
function PANEL:SetShowReset(value, name, default)
value = tobool(value)
if (value and !IsValid(self.reset)) then
self.reset = self:Add("DButton")
self.reset:SetFont("ixSmallTitleIcons")
self.reset:SetText("x")
self.reset:SetTextColor(ColorAlpha(derma.GetColor("Warning", self), 100))
self.reset:Dock(LEFT)
self.reset:DockMargin(4, 0, 0, 0)
self.reset:SizeToContents()
self.reset.Paint = nil
self.reset.DoClick = function()
self:OnResetClicked()
end
self.reset:SetHelixTooltip(function(tooltip)
local title = tooltip:AddRow("title")
title:SetImportant()
title:SetText(L("resetDefault"))
title:SetBackgroundColor(derma.GetColor("Warning", self))
title:SizeToContents()
local description = tooltip:AddRow("description")
description:SetText(L("resetDefaultDescription", tostring(name), tostring(default)))
description:SizeToContents()
end)
elseif (!value and IsValid(self.reset)) then
self.reset:Remove()
end
self.bShowReset = value
end
function PANEL:Think()
if (IsValid(self.reset)) then
self.reset:SetVisible(self:IsHovered() or self:IsOurChild(vgui.GetHoveredPanel()))
end
end
function PANEL:OnResetClicked()
end
function PANEL:GetLabel()
return self.text
end
function PANEL:SetText(text)
self.text:SetText(text)
self:SizeToContents()
end
function PANEL:GetText()
return self.text:GetText()
end
-- implemented by row types
function PANEL:GetValue()
end
function PANEL:SetValue(value)
end
-- meant for array types to populate combo box values
function PANEL:Populate(key, info)
end
-- called when value is changed by user
function PANEL:OnValueChanged(newValue)
end
function PANEL:SizeToContents()
local _, top, _, bottom = self:GetDockPadding()
self.text:SizeToContents()
self:SetTall(self.text:GetTall() + top + bottom)
self.ixRealHeight = self:GetTall()
self.ixHeight = self.ixRealHeight
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintSettingsRowBackground", self, width, height)
end
vgui.Register("ixSettingsRow", PANEL, "EditablePanel")
-- settings panel
PANEL = {}
function PANEL:Init()
self:Dock(FILL)
self.rows = {}
self.categories = {}
-- scroll panel
DEFINE_BASECLASS("DScrollPanel")
self.canvas = self:Add("DScrollPanel")
self.canvas:Dock(FILL)
self.canvas.PerformLayout = function(panel)
BaseClass.PerformLayout(panel)
if (!panel.VBar.Enabled) then
panel.pnlCanvas:SetWide(panel:GetWide() - panel.VBar:GetWide())
end
end
end
function PANEL:GetRowPanelName(type)
return panelMap[type] or "ixSettingsRow"
end
function PANEL:AddCategory(name)
local panel = self.categories[name]
if (!IsValid(panel)) then
panel = self.canvas:Add("ixCategoryPanel")
panel:SetText(name)
panel:Dock(TOP)
panel:DockMargin(0, 8, 0, 0)
self.categories[name] = panel
return panel
end
end
function PANEL:AddRow(type, category)
category = self.categories[category]
local id = panelMap[type]
if (!id) then
ErrorNoHalt("attempted to create row with unimplemented type '" .. tostring(ix.type[type]) .. "'\n")
id = "ixSettingsRow"
end
local panel = (IsValid(category) and category or self.canvas):Add(id)
panel:Dock(TOP)
panel:SetBackgroundIndex(#self.rows % 2)
self.rows[#self.rows + 1] = panel
return panel
end
function PANEL:GetRows()
return self.rows
end
function PANEL:Clear()
for _, v in ipairs(self.rows) do
if (IsValid(v)) then
v:Remove()
end
end
self.rows = {}
end
function PANEL:SetSearchEnabled(bValue)
if (!bValue) then
if (IsValid(self.searchEntry)) then
self.searchEntry:Remove()
end
return
end
-- search entry
self.searchEntry = self:Add("ixIconTextEntry")
self.searchEntry:Dock(TOP)
self.searchEntry:SetEnterAllowed(false)
self.searchEntry.OnChange = function(entry)
self:FilterRows(entry:GetValue())
end
end
function PANEL:FilterRows(query)
query = string.PatternSafe(query:lower())
local bEmpty = query == ""
for categoryName, category in pairs(self.categories) do
category.size = 0
category:CreateAnimation(0.5, {
index = 21,
target = {size = 1},
Think = function(animation, panel)
panel:SizeToContents()
end
})
for _, row in ipairs(category:GetChildren()) do
local bFound = bEmpty or row:GetText():lower():find(query) or categoryName:lower():find(query)
row:SetVisible(true)
row:CreateAnimation(0.5, {
index = 21,
target = {ixHeight = bFound and row.ixRealHeight or 0},
easing = "outQuint",
Think = function(animation, panel)
panel:SetTall(bFound and math.min(panel.ixHeight + 2, panel.ixRealHeight) or math.max(panel.ixHeight - 2, 0))
end,
OnComplete = function(animation, panel)
panel:SetVisible(bFound)
-- need this so categories are sized properly when animations are disabled - there is no guaranteed order
-- that animations will think so we SizeToContents here. putting it here will result in redundant calls but
-- I guess we have the performance to spare
if (ix.option.Get("disableAnimations", false)) then
category:SizeToContents()
end
end
})
end
end
end
function PANEL:Paint(width, height)
end
function PANEL:SizeToContents()
for _, v in pairs(self.categories) do
v:SizeToContents()
end
end
vgui.Register("ixSettings", PANEL, "Panel")
hook.Add("CreateMenuButtons", "ixSettings", function(tabs)
tabs["settings"] = {
PopulateTabButton = function(info, button)
local menu = ix.gui.menu
if (!IsValid(menu)) then
return
end
DEFINE_BASECLASS("ixMenuButton")
button:SetZPos(9999)
button.Paint = function(panel, width, height)
BaseClass.Paint(panel, width, height)
surface.SetDrawColor(255, 255, 255, 33)
surface.DrawRect(0, 0, width, 1)
end
end,
Create = function(info, container)
local panel = container:Add("ixSettings")
panel:SetSearchEnabled(true)
for category, options in SortedPairs(ix.option.GetAllByCategories(true)) do
category = L(category)
panel:AddCategory(category)
-- sort options by language phrase rather than the key
table.sort(options, function(a, b)
return L(a.phrase) < L(b.phrase)
end)
for _, data in pairs(options) do
local key = data.key
local row = panel:AddRow(data.type, category)
local value = ix.util.SanitizeType(data.type, ix.option.Get(key))
row:SetText(L(data.phrase))
row:Populate(key, data)
-- type-specific properties
if (data.type == ix.type.number) then
row:SetMin(data.min or 0)
row:SetMax(data.max or 10)
row:SetDecimals(data.decimals or 0)
end
row:SetValue(value, true)
row:SetShowReset(value != data.default, key, data.default)
row.OnValueChanged = function()
local newValue = row:GetValue()
row:SetShowReset(newValue != data.default, key, data.default)
ix.option.Set(key, newValue)
end
row.OnResetClicked = function()
row:SetShowReset(false)
row:SetValue(data.default, true)
ix.option.Set(key, data.default)
end
row:GetLabel():SetHelixTooltip(function(tooltip)
local title = tooltip:AddRow("name")
title:SetImportant()
title:SetText(key)
title:SizeToContents()
title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5))
local description = tooltip:AddRow("description")
description:SetText(L(data.description))
description:SizeToContents()
end)
end
end
panel:SizeToContents()
container.panel = panel
end,
OnSelected = function(info, container)
container.panel.searchEntry:RequestFocus()
end
}
end)
================================================
FILE: gamemode/core/derma/cl_shipment.lua
================================================
local PANEL = {}
function PANEL:Init()
self:SetSize(460, 360)
self:SetTitle(L"shipment")
self:Center()
self:MakePopup()
self.scroll = self:Add("DScrollPanel")
self.scroll:Dock(FILL)
self.list = self.scroll:Add("DListLayout")
self.list:Dock(FILL)
end
function PANEL:SetItems(entity, items)
self.entity = entity
self.items = true
self.itemPanels = {}
for k, v in SortedPairs(items) do
local itemTable = ix.item.list[k]
if (itemTable) then
local item = self.list:Add("DPanel")
item:SetTall(36)
item:Dock(TOP)
item:DockMargin(4, 4, 4, 0)
item.icon = item:Add("SpawnIcon")
item.icon:SetPos(2, 2)
item.icon:SetSize(32, 32)
item.icon:SetModel(itemTable:GetModel())
item.icon:SetHelixTooltip(function(tooltip)
ix.hud.PopulateItemTooltip(tooltip, itemTable)
end)
item.quantity = item.icon:Add("DLabel")
item.quantity:SetSize(32, 32)
item.quantity:SetContentAlignment(3)
item.quantity:SetTextInset(0, 0)
item.quantity:SetText(v)
item.quantity:SetFont("DermaDefaultBold")
item.quantity:SetExpensiveShadow(1, Color(0, 0, 0, 150))
item.name = item:Add("DLabel")
item.name:SetPos(38, 0)
item.name:SetSize(200, 36)
item.name:SetFont("ixSmallFont")
item.name:SetText(L(itemTable.name))
item.name:SetContentAlignment(4)
item.name:SetTextColor(color_white)
item.take = item:Add("DButton")
item.take:Dock(RIGHT)
item.take:SetText(L"take")
item.take:SetWide(48)
item.take:DockMargin(3, 3, 3, 3)
item.take:SetTextColor(color_white)
item.take.DoClick = function(this)
net.Start("ixShipmentUse")
net.WriteString(k)
net.WriteBool(false)
net.SendToServer()
items[k] = items[k] - 1
item.quantity:SetText(items[k])
if (items[k] <= 0) then
item:Remove()
items[k] = nil
end
if (table.IsEmpty(items)) then
self:Remove()
end
end
item.drop = item:Add("DButton")
item.drop:Dock(RIGHT)
item.drop:SetText(L"drop")
item.drop:SetWide(48)
item.drop:DockMargin(3, 3, 0, 3)
item.drop:SetTextColor(color_white)
item.drop.DoClick = function(this)
net.Start("ixShipmentUse")
net.WriteString(k)
net.WriteBool(true)
net.SendToServer()
items[k] = items[k] - 1
item.quantity:SetText(items[k])
if (items[k] <= 0) then
item:Remove()
end
end
self.itemPanels[k] = item
end
end
end
function PANEL:Close()
net.Start("ixShipmentClose")
net.SendToServer()
self:Remove()
end
function PANEL:Think()
if (self.items and !IsValid(self.entity)) then
self:Remove()
end
end
vgui.Register("ixShipment", PANEL, "DFrame")
================================================
FILE: gamemode/core/derma/cl_spawnicon.lua
================================================
DEFINE_BASECLASS("DModelPanel")
local PANEL = {}
function PANEL:Init()
self.defaultEyeTarget = Vector(0, 0, 64)
self:SetHidden(false)
for i = 0, 5 do
if (i == 1 or i == 5) then
self:SetDirectionalLight(i, Color(155, 155, 155))
else
self:SetDirectionalLight(i, Color(255, 255, 255))
end
end
end
function PANEL:SetModel(model, skin, hidden)
BaseClass.SetModel(self, model)
local entity = self.Entity
if (skin) then
entity:SetSkin(skin)
end
local sequence = entity:SelectWeightedSequence(ACT_IDLE)
if (sequence <= 0) then
sequence = entity:LookupSequence("idle_unarmed")
end
if (sequence > 0) then
entity:ResetSequence(sequence)
else
local found = false
for _, v in ipairs(entity:GetSequenceList()) do
if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then
entity:ResetSequence(v)
found = true
break
end
end
if (!found) then
entity:ResetSequence(4)
end
end
local data = PositionSpawnIcon(entity, entity:GetPos())
if (data) then
self:SetFOV(data.fov)
self:SetCamPos(data.origin)
self:SetLookAng(data.angles)
end
entity:SetIK(false)
entity:SetEyeTarget(self.defaultEyeTarget)
end
function PANEL:SetHidden(hidden)
if (hidden) then
self:SetAmbientLight(color_black)
self:SetColor(Color(0, 0, 0))
for i = 0, 5 do
self:SetDirectionalLight(i, color_black)
end
else
self:SetAmbientLight(Color(20, 20, 20))
self:SetAlpha(255)
for i = 0, 5 do
if (i == 1 or i == 5) then
self:SetDirectionalLight(i, Color(155, 155, 155))
else
self:SetDirectionalLight(i, Color(255, 255, 255))
end
end
end
end
function PANEL:LayoutEntity()
self:RunAnimation()
end
function PANEL:OnMousePressed()
if (self.DoClick) then
self:DoClick()
end
end
vgui.Register("ixSpawnIcon", PANEL, "DModelPanel")
================================================
FILE: gamemode/core/derma/cl_storage.lua
================================================
local PANEL = {}
AccessorFunc(PANEL, "money", "Money", FORCE_NUMBER)
function PANEL:Init()
self:DockPadding(1, 1, 1, 1)
self:SetTall(64)
self:Dock(BOTTOM)
self.moneyLabel = self:Add("DLabel")
self.moneyLabel:Dock(TOP)
self.moneyLabel:SetFont("ixGenericFont")
self.moneyLabel:SetText("")
self.moneyLabel:SetTextInset(2, 0)
self.moneyLabel:SizeToContents()
self.moneyLabel.Paint = function(panel, width, height)
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ix.config.Get("color"))
end
self.amountEntry = self:Add("ixTextEntry")
self.amountEntry:Dock(FILL)
self.amountEntry:SetFont("ixGenericFont")
self.amountEntry:SetNumeric(true)
self.amountEntry:SetValue("0")
self.transferButton = self:Add("DButton")
self.transferButton:SetFont("ixIconsMedium")
self:SetLeft(false)
self.transferButton.DoClick = function()
local amount = math.max(0, math.Round(tonumber(self.amountEntry:GetValue()) or 0))
self.amountEntry:SetValue("0")
if (amount != 0) then
self:OnTransfer(amount)
end
end
self.bNoBackgroundBlur = true
end
function PANEL:SetLeft(bValue)
if (bValue) then
self.transferButton:Dock(LEFT)
self.transferButton:SetText("s")
else
self.transferButton:Dock(RIGHT)
self.transferButton:SetText("t")
end
end
function PANEL:SetMoney(money)
local name = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "")
self.money = math.max(math.Round(tonumber(money) or 0), 0)
self.moneyLabel:SetText(string.format("%s: %d", name, money))
end
function PANEL:OnTransfer(amount)
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintBaseFrame", self, width, height)
end
vgui.Register("ixStorageMoney", PANEL, "EditablePanel")
DEFINE_BASECLASS("Panel")
PANEL = {}
AccessorFunc(PANEL, "fadeTime", "FadeTime", FORCE_NUMBER)
AccessorFunc(PANEL, "frameMargin", "FrameMargin", FORCE_NUMBER)
AccessorFunc(PANEL, "storageID", "StorageID", FORCE_NUMBER)
function PANEL:Init()
if (IsValid(ix.gui.openedStorage)) then
ix.gui.openedStorage:Remove()
end
ix.gui.openedStorage = self
self:SetSize(ScrW(), ScrH())
self:SetPos(0, 0)
self:SetFadeTime(0.25)
self:SetFrameMargin(4)
self.storageInventory = self:Add("ixInventory")
self.storageInventory.bNoBackgroundBlur = true
self.storageInventory:ShowCloseButton(true)
self.storageInventory:SetTitle("Storage")
self.storageInventory.Close = function(this)
net.Start("ixStorageClose")
net.SendToServer()
self:Remove()
end
self.storageMoney = self.storageInventory:Add("ixStorageMoney")
self.storageMoney:SetVisible(false)
self.storageMoney.OnTransfer = function(_, amount)
net.Start("ixStorageMoneyTake")
net.WriteUInt(self.storageID, 32)
net.WriteUInt(amount, 32)
net.SendToServer()
end
ix.gui.inv1 = self:Add("ixInventory")
ix.gui.inv1.bNoBackgroundBlur = true
ix.gui.inv1:ShowCloseButton(true)
ix.gui.inv1.Close = function(this)
net.Start("ixStorageClose")
net.SendToServer()
self:Remove()
end
self.localMoney = ix.gui.inv1:Add("ixStorageMoney")
self.localMoney:SetVisible(false)
self.localMoney:SetLeft(true)
self.localMoney.OnTransfer = function(_, amount)
net.Start("ixStorageMoneyGive")
net.WriteUInt(self.storageID, 32)
net.WriteUInt(amount, 32)
net.SendToServer()
end
self:SetAlpha(0)
self:AlphaTo(255, self:GetFadeTime())
self.storageInventory:MakePopup()
ix.gui.inv1:MakePopup()
end
function PANEL:OnChildAdded(panel)
panel:SetPaintedManually(true)
end
function PANEL:SetLocalInventory(inventory)
if (IsValid(ix.gui.inv1) and !IsValid(ix.gui.menu)) then
ix.gui.inv1:SetInventory(inventory)
ix.gui.inv1:SetPos(self:GetWide() / 2 + self:GetFrameMargin() / 2, self:GetTall() / 2 - ix.gui.inv1:GetTall() / 2)
end
end
function PANEL:SetLocalMoney(money)
if (!self.localMoney:IsVisible()) then
self.localMoney:SetVisible(true)
ix.gui.inv1:SetTall(ix.gui.inv1:GetTall() + self.localMoney:GetTall() + 2)
end
self.localMoney:SetMoney(money)
end
function PANEL:SetStorageTitle(title)
self.storageInventory:SetTitle(title)
end
function PANEL:SetStorageInventory(inventory)
self.storageInventory:SetInventory(inventory)
self.storageInventory:SetPos(
self:GetWide() / 2 - self.storageInventory:GetWide() - 2,
self:GetTall() / 2 - self.storageInventory:GetTall() / 2
)
ix.gui["inv" .. inventory:GetID()] = self.storageInventory
end
function PANEL:SetStorageMoney(money)
if (!self.storageMoney:IsVisible()) then
self.storageMoney:SetVisible(true)
self.storageInventory:SetTall(self.storageInventory:GetTall() + self.storageMoney:GetTall() + 2)
end
self.storageMoney:SetMoney(money)
end
function PANEL:Paint(width, height)
ix.util.DrawBlurAt(0, 0, width, height)
for _, v in ipairs(self:GetChildren()) do
v:PaintManual()
end
end
function PANEL:Remove()
self:SetAlpha(255)
self:AlphaTo(0, self:GetFadeTime(), 0, function()
BaseClass.Remove(self)
end)
end
function PANEL:OnRemove()
if (!IsValid(ix.gui.menu)) then
self.storageInventory:Remove()
ix.gui.inv1:Remove()
end
end
vgui.Register("ixStorageView", PANEL, "Panel")
================================================
FILE: gamemode/core/derma/cl_subpanel.lua
================================================
local DEFAULT_PADDING = ScreenScale(32)
local DEFAULT_ANIMATION_TIME = 1
local DEFAULT_SUBPANEL_ANIMATION_TIME = 0.5
-- parent subpanel
local PANEL = {}
function PANEL:Init()
local parent = self:GetParent()
local padding = parent.GetPadding and parent:GetPadding() or DEFAULT_PADDING
self:SetSize(parent:GetWide() - (padding * 2), parent:GetTall() - (padding * 2))
self:Center()
end
function PANEL:SetTitle(text, bNoTranslation, bNoUpper)
if (text == nil) then
if (IsValid(self.title)) then
self.title:Remove()
end
return
elseif (!IsValid(self.title)) then
self.title = self:Add("DLabel")
self.title:SetFont("ixTitleFont")
self.title:SizeToContents()
self.title:SetTextColor(ix.config.Get("color") or color_white)
self.title:Dock(TOP)
end
local newText = bNoTranslation and text or L(text)
newText = bNoUpper and newText or newText:utf8upper()
self.title:SetText(newText)
self.title:SizeToContents()
end
function PANEL:SetLeftPanel(panel)
self.left = panel
end
function PANEL:GetLeftPanel()
return self.left
end
function PANEL:SetRightPanel(panel)
self.right = panel
end
function PANEL:GetRightPanel()
return self.right
end
function PANEL:OnSetActive()
end
vgui.Register("ixSubpanel", PANEL, "EditablePanel")
-- subpanel parent
DEFINE_BASECLASS("EditablePanel")
PANEL = {}
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
AccessorFunc(PANEL, "subpanelAnimationTime", "SubpanelAnimationTime", FORCE_NUMBER)
AccessorFunc(PANEL, "leftOffset", "LeftOffset", FORCE_NUMBER)
function PANEL:Init()
self.subpanels = {}
self.childPanels = {}
self.currentSubpanelX = DEFAULT_PADDING
self.targetSubpanelX = DEFAULT_PADDING
self.padding = DEFAULT_PADDING
self.leftOffset = 0
self.animationTime = DEFAULT_ANIMATION_TIME
self.subpanelAnimationTime = DEFAULT_SUBPANEL_ANIMATION_TIME
end
function PANEL:SetPadding(amount, bSetDockPadding)
self.currentSubpanelX = amount
self.targetSubpanelX = amount
self.padding = amount
if (bSetDockPadding) then
self:DockPadding(amount, amount, amount, amount)
end
end
function PANEL:Add(name)
local panel = BaseClass.Add(self, name)
if (panel.SetPaintedManually) then
panel:SetPaintedManually(true)
self.childPanels[#self.childPanels + 1] = panel
end
return panel
end
function PANEL:AddSubpanel(name)
local id = #self.subpanels + 1
local panel = BaseClass.Add(self, "ixSubpanel")
panel.subpanelName = name
panel.subpanelID = id
panel:SetTitle(name)
self.subpanels[id] = panel
self:SetupSubpanelReferences()
return panel
end
function PANEL:SetupSubpanelReferences()
local lastPanel
for i = 1, #self.subpanels do
local panel = self.subpanels[i]
local nextPanel = self.subpanels[i + 1]
if (IsValid(lastPanel)) then
lastPanel:SetRightPanel(panel)
panel:SetLeftPanel(lastPanel)
end
if (IsValid(nextPanel)) then
panel:SetRightPanel(nextPanel)
end
lastPanel = panel
end
end
function PANEL:SetSubpanelPos(id, x)
local currentPanel = self.subpanels[id]
if (!currentPanel) then
return
end
local _, oldY = currentPanel:GetPos()
currentPanel:SetPos(x, oldY)
-- traverse left
while (IsValid(currentPanel)) do
local left = currentPanel:GetLeftPanel()
if (IsValid(left)) then
left:MoveLeftOf(currentPanel, self.padding + self.leftOffset)
end
currentPanel = left
end
currentPanel = self.subpanels[id]
-- traverse right
while (IsValid(currentPanel)) do
local right = currentPanel:GetRightPanel()
if (IsValid(right)) then
right:MoveRightOf(currentPanel, self.padding)
end
currentPanel = right
end
end
function PANEL:SetActiveSubpanel(id, length)
if (isstring(id)) then
for i = 1, #self.subpanels do
if (self.subpanels[i].subpanelName == id) then
id = i
break
end
end
end
local activePanel = self.subpanels[id]
if (!activePanel) then
return false
end
if (length == 0 or !self.activeSubpanel) then
self:SetSubpanelPos(id, self.padding + self.leftOffset)
else
local x, _ = activePanel:GetPos()
local target = self.targetSubpanelX + self.leftOffset
self.currentSubpanelX = x + self.padding + self.leftOffset
self:CreateAnimation(length or self.subpanelAnimationTime, {
index = 420,
target = {currentSubpanelX = target},
easing = "outQuint",
Think = function(animation, panel)
panel:SetSubpanelPos(id, panel.currentSubpanelX)
end,
OnComplete = function(animation, panel)
panel:SetSubpanelPos(id, target)
end
})
end
self.activeSubpanel = id
activePanel:OnSetActive()
return true
end
function PANEL:GetSubpanel(id)
return self.subpanels[id]
end
function PANEL:GetActiveSubpanel()
return self.subpanels[self.activeSubpanel]
end
function PANEL:GetActiveSubpanelID()
return self.activeSubpanel
end
function PANEL:Slide(direction, length, callback, bIgnoreConfig)
local _, height = self:GetParent():GetSize()
local x, _ = self:GetPos()
local targetY = direction == "up" and 0 or height
self:SetVisible(true)
if (length == 0) then
self:SetPos(x, targetY)
else
length = length or self.animationTime
self.currentY = direction == "up" and height or 0
self:CreateAnimation(length or self.animationTime, {
index = -1,
target = {currentY = targetY},
easing = "outExpo",
bIgnoreConfig = bIgnoreConfig,
Think = function(animation, panel)
local currentX, _ = panel:GetPos()
panel:SetPos(currentX, panel.currentY)
end,
OnComplete = function(animation, panel)
if (direction == "down") then
panel:SetVisible(false)
end
if (callback) then
callback(panel)
end
end
})
end
end
function PANEL:SlideUp(...)
self:SetMouseInputEnabled(true)
self:SetKeyboardInputEnabled(true)
self:OnSlideUp()
self:Slide("up", ...)
end
function PANEL:SlideDown(...)
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
self:OnSlideDown()
self:Slide("down", ...)
end
function PANEL:OnSlideUp()
end
function PANEL:OnSlideDown()
end
function PANEL:Paint(width, height)
for i = 1, #self.childPanels do
if not (IsValid(self.childPanels[i])) then
continue
end
self.childPanels[i]:PaintManual()
end
end
function PANEL:PaintSubpanels(width, height)
for i = 1, #self.subpanels do
if not (IsValid(self.subpanels[i])) then
continue
end
self.subpanels[i]:PaintManual()
end
end
-- ????
PANEL.Remove = BaseClass.Remove
vgui.Register("ixSubpanelParent", PANEL, "EditablePanel")
================================================
FILE: gamemode/core/derma/cl_tooltip.lua
================================================
--- Text container for `ixTooltip`.
-- Rows are the main way of interacting with `ixTooltip`s. These derive from
-- [DLabel](https://wiki.garrysmod.com/page/Category:DLabel) panels, which means that making use of this panel
-- will be largely the same as any DLabel panel.
-- @panel ixTooltipRow
local animationTime = 1
-- panel meta
do
local PANEL = FindMetaTable("Panel")
local ixChangeTooltip = ChangeTooltip
local ixRemoveTooltip = RemoveTooltip
local tooltip
local lastHover
function PANEL:SetHelixTooltip(callback)
self:SetMouseInputEnabled(true)
self.ixTooltip = callback
end
function ChangeTooltip(panel, ...) -- luacheck: globals ChangeTooltip
if (!panel.ixTooltip) then
return ixChangeTooltip(panel, ...)
end
RemoveTooltip()
timer.Create("ixTooltip", 0.1, 1, function()
if (!IsValid(panel) or lastHover != panel) then
return
end
tooltip = vgui.Create("ixTooltip")
panel.ixTooltip(tooltip)
tooltip:SizeToContents()
end)
lastHover = panel
end
function RemoveTooltip() -- luacheck: globals RemoveTooltip
if (IsValid(tooltip)) then
tooltip:Remove()
tooltip = nil
end
timer.Remove("ixTooltip")
lastHover = nil
return ixRemoveTooltip()
end
end
DEFINE_BASECLASS("DLabel")
local PANEL = {}
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER)
AccessorFunc(PANEL, "bNoMinimal", "MinimalHidden", FORCE_BOOL)
function PANEL:Init()
self:SetFont("ixSmallFont")
self:SetText(L("unknown"))
self:SetTextColor(color_white)
self:SetTextInset(4, 0)
self:SetContentAlignment(4)
self:Dock(TOP)
self.maxWidth = ScrW() * 0.2
self.bNoMinimal = false
self.bMinimal = false
end
--- Whether or not this tooltip row should be displayed in a minimal format. This usually means no background and/or
-- smaller font. You probably won't need this if you're using regular `ixTooltipRow` panels, but you should take into
-- account if you're creating your own panels that derive from `ixTooltipRow`.
-- @realm client
-- @treturn bool True if this tooltip row should be displayed in a minimal format
function PANEL:IsMinimal()
return self.bMinimal
end
--- Sets this row to be more prominent with a larger font and more noticable background color. This should usually
-- be used once per tooltip as a title row. For example, item tooltips have one "important" row consisting of the
-- item's name. Note that this function is a fire-and-forget function; you cannot revert a row back to it's regular state
-- unless you set the font/colors manually.
-- @realm client
function PANEL:SetImportant()
self:SetFont("ixSmallTitleFont")
self:SetExpensiveShadow(1, color_black)
self:SetBackgroundColor(ix.config.Get("color"))
end
--- Sets the background color of this row. This should be used sparingly to avoid overwhelming players with a
-- bunch of different colors that could convey different meanings.
-- @realm client
-- @color color New color of the background. The alpha is clamped to 100-255 to ensure visibility
function PANEL:SetBackgroundColor(color)
color = table.Copy(color)
color.a = math.min(color.a or 255, 100)
self.backgroundColor = color
end
--- Resizes this panel to fit its contents. This should be called after setting the text.
-- @realm client
function PANEL:SizeToContents()
local contentWidth, contentHeight = self:GetContentSize()
contentWidth = contentWidth + 4
contentHeight = contentHeight + 4
if (contentWidth > self.maxWidth) then
self:SetWide(self.maxWidth - 4) -- to account for text inset
self:SetTextInset(4, 0)
self:SetWrap(true)
self:SizeToContentsY()
else
self:SetSize(contentWidth, contentHeight)
end
end
--- Resizes the height of this panel to fit its contents.
-- @internal
-- @realm client
function PANEL:SizeToContentsY()
BaseClass.SizeToContentsY(self)
self:SetTall(self:GetTall() + 4)
end
--- Called when the background of this row should be painted. This will paint the background with the
-- `DrawImportantBackground` function set in the skin by default.
-- @realm client
-- @number width Width of the panel
-- @number height Height of the panel
function PANEL:PaintBackground(width, height)
if (self.backgroundColor) then
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor)
end
end
--- Called when the foreground of this row should be painted. If you are overriding this in a subclassed panel,
-- make sure you call `ixTooltipRow:PaintBackground` at the *beginning* of your function to make its style
-- consistent with the rest of the framework.
-- @realm client
-- @number width Width of the panel
-- @number height Height of the panel
function PANEL:Paint(width, height)
self:PaintBackground(width, height)
end
vgui.Register("ixTooltipRow", PANEL, "DLabel")
--- Generic information panel.
-- Tooltips are used extensively throughout Helix: for item information, character displays, entity status, etc.
-- The tooltip system can be used on any panel or entity you would like to show standardized information for. Tooltips
-- consist of the parent container panel (`ixTooltip`), which is filled with rows of information (usually
-- `ixTooltipRow`, but can be any docked panel if non-text information needs to be shown, like an item's size).
--
-- Tooltips can be added to panel with `panel:SetHelixTooltip()`. An example taken from the scoreboard:
-- panel:SetHelixTooltip(function(tooltip)
-- local name = tooltip:AddRow("name")
-- name:SetImportant()
-- name:SetText(client:SteamName())
-- name:SetBackgroundColor(team.GetColor(client:Team()))
-- name:SizeToContents()
--
-- tooltip:SizeToContents()
-- end)
-- @panel ixTooltip
DEFINE_BASECLASS("Panel")
PANEL = {}
AccessorFunc(PANEL, "entity", "Entity")
AccessorFunc(PANEL, "mousePadding", "MousePadding", FORCE_NUMBER)
AccessorFunc(PANEL, "bDrawArrow", "DrawArrow", FORCE_BOOL)
AccessorFunc(PANEL, "arrowColor", "ArrowColor")
AccessorFunc(PANEL, "bHideArrowWhenRaised", "HideArrowWhenRaised", FORCE_BOOL)
AccessorFunc(PANEL, "bArrowFollowEntity", "ArrowFollowEntity", FORCE_BOOL)
function PANEL:Init()
self.fraction = 0
self.mousePadding = 16
self.arrowColor = ix.config.Get("color")
self.bHideArrowWhenRaised = true
self.bArrowFollowEntity = true
self.bMinimal = false
self.lastX, self.lastY = self:GetCursorPosition()
self.arrowX, self.arrowY = ScrW() * 0.5, ScrH() * 0.5
self:SetAlpha(0)
self:SetSize(0, 0)
self:SetDrawOnTop(true)
self:SetMouseInputEnabled(false)
self:CreateAnimation(animationTime, {
index = 1,
target = {fraction = 1},
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(panel.fraction * 255)
end
})
end
--- Whether or not this tooltip should be displayed in a minimal format.
-- @realm client
-- @treturn bool True if this tooltip should be displayed in a minimal format
-- @see ixTooltipRow:IsMinimal
function PANEL:IsMinimal()
return self.bMinimal
end
-- ensure all children are painted manually
function PANEL:Add(...)
local panel = BaseClass.Add(self, ...)
panel:SetPaintedManually(true)
return panel
end
--- Creates a new `ixTooltipRow` panel and adds it to the bottom of this tooltip.
-- @realm client
-- @string id Name of the new row. This is used to reorder rows if needed
-- @treturn panel Created row
function PANEL:AddRow(id)
local panel = self:Add("ixTooltipRow")
panel.id = id
panel:SetZPos(#self:GetChildren() * 10)
return panel
end
--- Creates a new `ixTooltipRow` and adds it after the row with the given `id`. The order of the rows is set via
-- setting the Z position of the panels, as this is how VGUI handles ordering with docked panels.
-- @realm client
-- @string after Name of the row to insert after
-- @string id Name of the newly created row
-- @treturn panel Created row
function PANEL:AddRowAfter(after, id)
local panel = self:AddRow(id)
after = self:GetRow(after)
if (!IsValid(after)) then
return panel
end
panel:SetZPos(after:GetZPos() + 1)
return panel
end
--- Sets the entity associated with this tooltip. Note that this function is not how you get entities to show tooltips.
-- @internal
-- @realm client
-- @entity entity Entity to associate with this tooltip
function PANEL:SetEntity(entity)
if (!IsValid(entity)) then
self.bEntity = false
return
end
-- don't show entity tooltips if we have an entity menu open
if (IsValid(ix.menu.panel)) then
self:Remove()
return
end
if (entity:IsPlayer()) then
local character = entity:GetCharacter()
if (character) then
-- we want to group things that will most likely have backgrounds (e.g name/health status)
hook.Run("PopulateImportantCharacterInfo", entity, character, self)
hook.Run("PopulateCharacterInfo", entity, character, self)
end
else
if (entity.OnPopulateEntityInfo) then
entity:OnPopulateEntityInfo(self)
else
hook.Run("PopulateEntityInfo", entity, self)
end
end
self:SizeToContents()
self.entity = entity
self.bEntity = true
end
function PANEL:PaintUnder(width, height)
end
function PANEL:Paint(width, height)
self:PaintUnder()
-- directional arrow
self.bRaised = LocalPlayer():IsWepRaised()
if (!self.bClosing) then
if (self.bEntity and IsValid(self.entity) and self.bArrowFollowEntity) then
local entity = self.entity
local position = select(1, entity:GetBonePosition(entity:LookupBone("ValveBiped.Bip01_Spine") or -1)) or
entity:LocalToWorld(entity:OBBCenter())
position = position:ToScreen()
self.arrowX = math.Clamp(position.x, 0, ScrW())
self.arrowY = math.Clamp(position.y, 0, ScrH())
end
end
-- arrow
if (self.bDrawArrow or (self.bDrawArrow and self.bRaised and !self.bHideArrowWhenRaised)) then
local x, y = self:ScreenToLocal(self.arrowX, self.arrowY)
DisableClipping(true)
surface.SetDrawColor(self.arrowColor)
surface.DrawLine(0, 0, x * self.fraction, y * self.fraction)
surface.DrawRect((x - 2) * self.fraction, (y - 2) * self.fraction, 4, 4)
DisableClipping(false)
end
-- contents
local x, y = self:GetPos()
render.SetScissorRect(x, y, x + width * self.fraction, y + height, true)
derma.SkinFunc("PaintTooltipBackground", self, width, height)
for _, v in ipairs(self:GetChildren()) do
if (IsValid(v)) then
v:PaintManual()
end
end
render.SetScissorRect(0, 0, 0, 0, false)
end
--- Returns the current position of the mouse cursor on the screen.
-- @realm client
-- @treturn number X position of cursor
-- @treturn number Y position of cursor
function PANEL:GetCursorPosition()
local width, height = self:GetSize()
local mouseX, mouseY = gui.MousePos()
return math.Clamp(mouseX + self.mousePadding, 0, ScrW() - width), math.Clamp(mouseY, 0, ScrH() - height)
end
function PANEL:Think()
if (!self.bEntity) then
if (!vgui.CursorVisible()) then
self:SetPos(self.lastX, self.lastY)
-- if the cursor isn't visible then we don't really need the tooltip to be shown
if (!self.bClosing) then
self:Remove()
end
else
local newX, newY = self:GetCursorPosition()
self:SetPos(newX, newY)
self.lastX, self.lastY = newX, newY
end
self:MoveToFront() -- dragging a panel w/ tooltip will push the tooltip beneath even the menu panel(???)
elseif (IsValid(self.entity) and !self.bClosing) then
if (self.bRaised) then
self:SetPos(
ScrW() * 0.5 - self:GetWide() * 0.5,
math.min(ScrH() * 0.5 + self:GetTall() + 32, ScrH() - self:GetTall())
)
else
local entity = self.entity
local min, max = entity:GetRotatedAABB(entity:OBBMins() * 0.5, entity:OBBMaxs() * 0.5)
min = entity:LocalToWorld(min):ToScreen().x
max = entity:LocalToWorld(max):ToScreen().x
self:SetPos(
math.Clamp(math.max(min, max), ScrW() * 0.5 + 64, ScrW() - self:GetWide()),
ScrH() * 0.5 - self:GetTall() * 0.5
)
end
end
end
--- Returns an `ixTooltipRow` corresponding to the given name.
-- @realm client
-- @string id Name of the row
-- @treturn[1] panel Corresponding row
-- @treturn[2] nil If the row doesn't exist
function PANEL:GetRow(id)
for _, v in ipairs(self:GetChildren()) do
if (IsValid(v) and v.id == id) then
return v
end
end
end
--- Resizes the tooltip to fit all of the child panels. You should always call this after you are done
-- adding all of your rows.
-- @realm client
function PANEL:SizeToContents()
local height = 0
local width = 0
for _, v in ipairs(self:GetChildren()) do
if (v:GetWide() > width) then
width = v:GetWide()
end
height = height + v:GetTall()
end
self:SetSize(width, height)
end
function PANEL:Remove()
if (self.bClosing) then
return
end
self.bClosing = true
self:CreateAnimation(animationTime * 0.5, {
target = {fraction = 0},
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(panel.fraction * 255)
end,
OnComplete = function(animation, panel)
BaseClass.Remove(panel)
end
})
end
vgui.Register("ixTooltip", PANEL, "Panel")
-- legacy tooltip row
PANEL = {}
function PANEL:Init()
self.bMinimal = true
self.ixAlpha = 0 -- to avoid conflicts if we're animating a non-tooltip panel
self:SetExpensiveShadow(1, color_black)
self:SetContentAlignment(5)
end
function PANEL:SetImportant()
self:SetFont("ixMinimalTitleFont")
self:SetBackgroundColor(ix.config.Get("color"))
end
-- background color will affect text instead in minimal tooltips
function PANEL:SetBackgroundColor(color)
color = table.Copy(color)
color.a = math.min(color.a or 255, 100)
self:SetTextColor(color)
self.backgroundColor = color
end
function PANEL:PaintBackground()
end
vgui.Register("ixTooltipMinimalRow", PANEL, "ixTooltipRow")
-- legacy tooltip
DEFINE_BASECLASS("ixTooltip")
PANEL = {}
function PANEL:Init()
self.bMinimal = true
-- we don't want to animate the alpha since children will handle their own animation, but we want to keep the fraction
-- for the background to animate
self:CreateAnimation(animationTime, {
index = 1,
target = {fraction = 1},
easing = "outQuint",
})
self:SetAlpha(255)
end
-- we don't need the children to be painted manually
function PANEL:Add(...)
local panel = BaseClass.Add(self, ...)
panel:SetPaintedManually(false)
return panel
end
function PANEL:AddRow(id)
local panel = self:Add("ixTooltipMinimalRow")
panel.id = id
panel:SetZPos(#self:GetChildren() * 10)
return panel
end
function PANEL:Paint(width, height)
self:PaintUnder()
derma.SkinFunc("PaintTooltipMinimalBackground", self, width, height)
end
function PANEL:Think()
end
function PANEL:SizeToContents()
-- remove any panels that shouldn't be shown in a minimal tooltip
for _, v in ipairs(self:GetChildren()) do
if (v.bNoMinimal) then
v:Remove()
end
end
BaseClass.SizeToContents(self)
self:SetPos(ScrW() * 0.5 - self:GetWide() * 0.5, ScrH() * 0.5 + self.mousePadding)
-- we create animation here since this is the only function that usually gets called after all the rows are populated
local children = self:GetChildren()
-- sort by z index so we can animate them in order
table.sort(children, function(a, b)
return a:GetZPos() < b:GetZPos()
end)
local i = 1
local count = table.Count(children)
for _, v in ipairs(children) do
v.ixAlpha = v.ixAlpha or 0
v:CreateAnimation((animationTime / count) * i, {
easing = "inSine",
target = {ixAlpha = 255},
Think = function(animation, panel)
panel:SetAlpha(panel.ixAlpha)
end
})
i = i + 1
end
end
DEFINE_BASECLASS("Panel")
function PANEL:Remove()
if (self.bClosing) then
return
end
self.bClosing = true
-- we create animation here since this is the only function that usually gets called after all the rows are populated
local children = self:GetChildren()
-- sort by z index so we can animate them in order
table.sort(children, function(a, b)
return a:GetZPos() > b:GetZPos()
end)
local duration = animationTime * 0.5
local i = 1
local count = table.Count(children)
for _, v in ipairs(children) do
v.ixAlpha = v.ixAlpha or 255
v:CreateAnimation(duration / count * i, {
target = {ixAlpha = 0},
Think = function(animation, panel)
panel:SetAlpha(panel.ixAlpha)
end
})
i = i + 1
end
self:CreateAnimation(duration, {
target = {fraction = 0},
OnComplete = function(animation, panel)
BaseClass.Remove(panel)
end
})
end
vgui.Register("ixTooltipMinimal", PANEL, "ixTooltip")
================================================
FILE: gamemode/core/hooks/cl_hooks.lua
================================================
function GM:ForceDermaSkin()
return "helix"
end
function GM:ScoreboardShow()
if (LocalPlayer():GetCharacter()) then
vgui.Create("ixMenu")
end
end
function GM:ScoreboardHide()
end
function GM:LoadFonts(font, genericFont)
surface.CreateFont("ix3D2DFont", {
font = font,
size = 128,
extended = true,
weight = 100
})
surface.CreateFont("ix3D2DMediumFont", {
font = font,
size = 48,
extended = true,
weight = 100
})
surface.CreateFont("ix3D2DSmallFont", {
font = font,
size = 24,
extended = true,
weight = 400
})
surface.CreateFont("ixTitleFont", {
font = font,
size = ScreenScale(30),
extended = true,
weight = 100
})
surface.CreateFont("ixSubTitleFont", {
font = font,
size = ScreenScale(16),
extended = true,
weight = 100
})
surface.CreateFont("ixMenuMiniFont", {
font = "Roboto",
size = math.max(ScreenScale(4), 18),
weight = 300,
})
surface.CreateFont("ixMenuButtonFont", {
font = "Roboto Th",
size = ScreenScale(14),
extended = true,
weight = 100
})
surface.CreateFont("ixMenuButtonFontSmall", {
font = "Roboto Th",
size = ScreenScale(10),
extended = true,
weight = 100
})
surface.CreateFont("ixMenuButtonFontThick", {
font = "Roboto",
size = ScreenScale(14),
extended = true,
weight = 300
})
surface.CreateFont("ixMenuButtonLabelFont", {
font = "Roboto Th",
size = 28,
extended = true,
weight = 100
})
surface.CreateFont("ixMenuButtonHugeFont", {
font = "Roboto Th",
size = ScreenScale(24),
extended = true,
weight = 100
})
surface.CreateFont("ixToolTipText", {
font = font,
size = 20,
extended = true,
weight = 500
})
surface.CreateFont("ixMonoSmallFont", {
font = "Consolas",
size = 12,
extended = true,
weight = 800
})
surface.CreateFont("ixMonoMediumFont", {
font = "Consolas",
size = 22,
extended = true,
weight = 800
})
-- The more readable font.
font = genericFont
surface.CreateFont("ixBigFont", {
font = font,
size = 36,
extended = true,
weight = 1000
})
surface.CreateFont("ixMediumFont", {
font = font,
size = 25,
extended = true,
weight = 1000
})
surface.CreateFont("ixNoticeFont", {
font = font,
size = math.max(ScreenScale(8), 18),
weight = 100,
extended = true,
antialias = true
})
surface.CreateFont("ixMediumLightFont", {
font = font,
size = 25,
extended = true,
weight = 200
})
surface.CreateFont("ixMediumLightBlurFont", {
font = font,
size = 25,
extended = true,
weight = 200,
blursize = 4
})
surface.CreateFont("ixGenericFont", {
font = font,
size = 20,
extended = true,
weight = 1000
})
surface.CreateFont("ixChatFont", {
font = font,
size = math.max(ScreenScale(7), 17) * ix.option.Get("chatFontScale", 1),
extended = true,
weight = 600,
antialias = true
})
surface.CreateFont("ixChatFontItalics", {
font = font,
size = math.max(ScreenScale(7), 17) * ix.option.Get("chatFontScale", 1),
extended = true,
weight = 600,
antialias = true,
italic = true
})
surface.CreateFont("ixSmallTitleFont", {
font = "Roboto Th",
size = math.max(ScreenScale(12), 24),
extended = true,
weight = 100
})
surface.CreateFont("ixMinimalTitleFont", {
font = "Roboto",
size = math.max(ScreenScale(8), 22),
extended = true,
weight = 800
})
surface.CreateFont("ixSmallFont", {
font = font,
size = math.max(ScreenScale(6), 17),
extended = true,
weight = 500
})
surface.CreateFont("ixItemDescFont", {
font = font,
size = math.max(ScreenScale(6), 17),
extended = true,
shadow = true,
weight = 500
})
surface.CreateFont("ixSmallBoldFont", {
font = font,
size = math.max(ScreenScale(8), 20),
extended = true,
weight = 800
})
surface.CreateFont("ixItemBoldFont", {
font = font,
shadow = true,
size = math.max(ScreenScale(8), 20),
extended = true,
weight = 800
})
-- Introduction fancy font.
font = "Roboto Th"
surface.CreateFont("ixIntroTitleFont", {
font = font,
size = math.min(ScreenScale(128), 128),
extended = true,
weight = 100
})
surface.CreateFont("ixIntroTitleBlurFont", {
font = font,
size = math.min(ScreenScale(128), 128),
extended = true,
weight = 100,
blursize = 4
})
surface.CreateFont("ixIntroSubtitleFont", {
font = font,
size = ScreenScale(24),
extended = true,
weight = 100
})
surface.CreateFont("ixIntroSmallFont", {
font = font,
size = ScreenScale(14),
extended = true,
weight = 100
})
surface.CreateFont("ixIconsSmall", {
font = "fontello",
size = 22,
extended = true,
weight = 500
})
surface.CreateFont("ixSmallTitleIcons", {
font = "fontello",
size = math.max(ScreenScale(11), 23),
extended = true,
weight = 100
})
surface.CreateFont("ixIconsMedium", {
font = "fontello",
extended = true,
size = 28,
weight = 500
})
surface.CreateFont("ixIconsMenuButton", {
font = "fontello",
size = ScreenScale(14),
extended = true,
weight = 100
})
surface.CreateFont("ixIconsBig", {
font = "fontello",
extended = true,
size = 48,
weight = 500
})
end
function GM:OnCharacterMenuCreated(panel)
if (IsValid(ix.gui.notices)) then
ix.gui.notices:Clear()
end
end
local LOWERED_ANGLES = Angle(30, 0, -25)
function GM:CalcViewModelView(weapon, viewModel, oldEyePos, oldEyeAngles, eyePos, eyeAngles)
if (!IsValid(weapon)) then
return
end
local client = LocalPlayer()
local bWepRaised = client:IsWepRaised()
-- update tween if the raised state is out of date
if (client.ixWasWeaponRaised != bWepRaised) then
local fraction = bWepRaised and 0 or 1
client.ixRaisedFraction = 1 - fraction
client.ixRaisedTween = ix.tween.new(0.75, client, {
ixRaisedFraction = fraction
}, "outQuint")
client.ixWasWeaponRaised = bWepRaised
end
local fraction = client.ixRaisedFraction
local rotation = weapon.LowerAngles or LOWERED_ANGLES
if (ix.option.Get("altLower", true) and weapon.LowerAngles2) then
rotation = weapon.LowerAngles2
end
eyeAngles:RotateAroundAxis(eyeAngles:Up(), rotation.p * fraction)
eyeAngles:RotateAroundAxis(eyeAngles:Forward(), rotation.y * fraction)
eyeAngles:RotateAroundAxis(eyeAngles:Right(), rotation.r * fraction)
viewModel:SetAngles(eyeAngles)
return self.BaseClass:CalcViewModelView(weapon, viewModel, oldEyePos, oldEyeAngles, eyePos, eyeAngles)
end
function GM:LoadIntro()
if (!IsValid(ix.gui.intro)) then
vgui.Create("ixIntro")
end
end
function GM:CharacterLoaded()
local menu = ix.gui.characterMenu
if (IsValid(menu)) then
menu:Close((LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()) and true or nil)
end
end
function GM:InitializedConfig()
local color = ix.config.Get("color")
hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont"))
hook.Run("ColorSchemeChanged", color)
if (!ix.config.loaded and !IsValid(ix.gui.loading)) then
local loader = vgui.Create("EditablePanel")
loader:ParentToHUD()
loader:Dock(FILL)
loader.Paint = function(this, w, h)
surface.SetDrawColor(0, 0, 0)
surface.DrawRect(0, 0, w, h)
end
local statusLabel = loader:Add("DLabel")
statusLabel:Dock(FILL)
statusLabel:SetText(L"loading")
statusLabel:SetFont("ixTitleFont")
statusLabel:SetContentAlignment(5)
statusLabel:SetTextColor(color_white)
timer.Simple(5, function()
if (IsValid(ix.gui.loading)) then
local fault = GetNetVar("dbError")
if (fault) then
statusLabel:SetText(fault and L"dbError" or L"loading")
local label = loader:Add("DLabel")
label:DockMargin(0, 64, 0, 0)
label:Dock(TOP)
label:SetFont("ixSubTitleFont")
label:SetText(fault)
label:SetContentAlignment(5)
label:SizeToContentsY()
label:SetTextColor(Color(255, 50, 50))
end
end
end)
ix.gui.loading = loader
ix.config.loaded = true
if (ix.config.Get("intro", true) and ix.option.Get("showIntro", true)) then
hook.Run("LoadIntro")
end
end
end
function GM:InitPostEntity()
ix.joinTime = RealTime() - 0.9716
ix.option.Sync()
ix.gui.bars = vgui.Create("ixInfoBarManager")
end
function GM:NetworkEntityCreated(entity)
if (entity:IsPlayer()) then
entity:SetIK(false)
-- we've just discovered a new player, so we need to update their animation state
if (entity != LocalPlayer()) then
-- we don't need to call the PlayerWeaponChanged hook here since it'll be handled below,
-- when this player's weapon has been discovered
hook.Run("PlayerModelChanged", entity, entity:GetModel())
end
elseif (entity:IsWeapon()) then
local owner = entity:GetOwner()
if (IsValid(owner) and owner:IsPlayer() and entity == owner:GetActiveWeapon()) then
hook.Run("PlayerWeaponChanged", owner, entity)
end
end
end
local vignette = ix.util.GetMaterial("helix/gui/vignette.png")
local vignetteAlphaGoal = 0
local vignetteAlphaDelta = 0
local vignetteTraceHeight = Vector(0, 0, 768)
local blurGoal = 0
local blurDelta = 0
local hasVignetteMaterial = !vignette:IsError()
timer.Create("ixVignetteChecker", 1, 0, function()
local client = LocalPlayer()
if (IsValid(client)) then
local data = {}
data.start = client:GetPos()
data.endpos = data.start + vignetteTraceHeight
data.filter = client
local trace = util.TraceLine(data)
-- this timer could run before InitPostEntity is called, so we have to check for the validity of the trace table
if (trace and trace.Hit) then
vignetteAlphaGoal = 80
else
vignetteAlphaGoal = 0
end
end
end)
function GM:CalcView(client, origin, angles, fov)
local view = self.BaseClass:CalcView(client, origin, angles, fov) or {}
local entity = Entity(client:GetLocalVar("ragdoll", 0))
local ragdoll = IsValid(client:GetRagdollEntity()) and client:GetRagdollEntity() or entity
if ((!client:ShouldDrawLocalPlayer() and IsValid(entity) and entity:IsRagdoll())
or (!LocalPlayer():Alive() and IsValid(ragdoll))) then
local ent = LocalPlayer():Alive() and entity or ragdoll
local index = ent:LookupAttachment("eyes")
if (index) then
local data = ent:GetAttachment(index)
if (data) then
view.origin = data.Pos
view.angles = data.Ang
end
return view
end
end
local menu = ix.gui.menu
local entityMenu = ix.menu.panel
if (IsValid(menu) and menu:IsVisible() and menu:GetCharacterOverview()) then
local newOrigin, newAngles, newFOV, bDrawPlayer = menu:GetOverviewInfo(origin, angles, fov)
view.drawviewer = bDrawPlayer
view.fov = newFOV
view.origin = newOrigin
view.angles = newAngles
elseif (IsValid(entityMenu)) then
view.angles = entityMenu:GetOverviewInfo(origin, angles)
end
return view
end
local hookRun = hook.Run
do
local aimLength = 0.35
local aimTime = 0
local aimEntity
local lastEntity
local lastTrace = {}
timer.Create("ixCheckTargetEntity", 0.1, 0, function()
local client = LocalPlayer()
local time = SysTime()
if (!IsValid(client)) then
return
end
local character = client:GetCharacter()
if (!character) then
return
end
lastTrace.start = client:GetShootPos()
lastTrace.endpos = lastTrace.start + client:GetAimVector(client) * 160
lastTrace.filter = client
lastTrace.mask = MASK_SHOT_HULL
lastEntity = util.TraceHull(lastTrace).Entity
if (lastEntity != aimEntity) then
aimTime = time + aimLength
aimEntity = lastEntity
end
local panel = ix.gui.entityInfo
local bShouldShow = time >= aimTime and (!IsValid(ix.gui.menu) or ix.gui.menu.bClosing) and
(!IsValid(ix.gui.characterMenu) or ix.gui.characterMenu.bClosing)
local bShouldPopulate = lastEntity.OnShouldPopulateEntityInfo and lastEntity:OnShouldPopulateEntityInfo() or true
if (bShouldShow and IsValid(lastEntity) and hookRun("ShouldPopulateEntityInfo", lastEntity) != false and
(lastEntity.PopulateEntityInfo or bShouldPopulate)) then
if (!IsValid(panel) or (IsValid(panel) and panel:GetEntity() != lastEntity)) then
if (IsValid(ix.gui.entityInfo)) then
ix.gui.entityInfo:Remove()
end
local infoPanel = vgui.Create(ix.option.Get("minimalTooltips", false) and "ixTooltipMinimal" or "ixTooltip")
local entityPlayer = lastEntity:GetNetVar("player")
if (entityPlayer) then
infoPanel:SetEntity(entityPlayer)
infoPanel.entity = lastEntity
else
infoPanel:SetEntity(lastEntity)
end
infoPanel:SetDrawArrow(true)
ix.gui.entityInfo = infoPanel
end
elseif (IsValid(panel)) then
panel:Remove()
end
end)
end
local mathApproach = math.Approach
local surface = surface
function GM:HUDPaintBackground()
local client = LocalPlayer()
if (!client:GetCharacter()) then
return
end
local frameTime = FrameTime()
local scrW, scrH = ScrW(), ScrH()
if (hasVignetteMaterial and ix.config.Get("vignette")) then
vignetteAlphaDelta = mathApproach(vignetteAlphaDelta, vignetteAlphaGoal, frameTime * 30)
surface.SetDrawColor(0, 0, 0, 175 + vignetteAlphaDelta)
surface.SetMaterial(vignette)
surface.DrawTexturedRect(0, 0, scrW, scrH)
end
blurGoal = client:GetLocalVar("blur", 0) + (hookRun("AdjustBlurAmount", blurGoal) or 0)
if (blurDelta != blurGoal) then
blurDelta = mathApproach(blurDelta, blurGoal, frameTime * 20)
end
if (blurDelta > 0 and !client:ShouldDrawLocalPlayer()) then
ix.util.DrawBlurAt(0, 0, scrW, scrH, blurDelta)
end
self.BaseClass:PaintWorldTips()
local weapon = client:GetActiveWeapon()
if (IsValid(weapon) and hook.Run("CanDrawAmmoHUD", weapon) != false and weapon.DrawAmmo != false) then
local clip = weapon:Clip1()
local clipMax = weapon:GetMaxClip1()
local count = client:GetAmmoCount(weapon:GetPrimaryAmmoType())
local secondary = client:GetAmmoCount(weapon:GetSecondaryAmmoType())
local x, y = scrW - 80, scrH - 80
if (secondary > 0) then
ix.util.DrawBlurAt(x, y, 64, 64)
surface.SetDrawColor(255, 255, 255, 5)
surface.DrawRect(x, y, 64, 64)
surface.SetDrawColor(255, 255, 255, 3)
surface.DrawOutlinedRect(x, y, 64, 64)
ix.util.DrawText(secondary, x + 32, y + 32, nil, 1, 1, "ixBigFont")
end
if (weapon:GetClass() != "weapon_slam" and clip > 0 or count > 0) then
x = x - (secondary > 0 and 144 or 64)
ix.util.DrawBlurAt(x, y, 128, 64)
surface.SetDrawColor(255, 255, 255, 5)
surface.DrawRect(x, y, 128, 64)
surface.SetDrawColor(255, 255, 255, 3)
surface.DrawOutlinedRect(x, y, 128, 64)
ix.util.DrawText((clip == -1 or clipMax == -1) and count or clip.."/"..count, x + 64, y + 32, nil, 1, 1, "ixBigFont")
end
end
if (client:GetLocalVar("restricted") and !client:GetLocalVar("restrictNoMsg")) then
ix.util.DrawText(L"restricted", scrW * 0.5, scrH * 0.33, nil, 1, 1, "ixBigFont")
end
end
function GM:PostDrawOpaqueRenderables(bDepth, bSkybox)
if (bDepth or bSkybox or #ix.blurRenderQueue == 0) then
return
end
ix.util.ResetStencilValues()
render.SetStencilEnable(true)
render.SetStencilWriteMask(27)
render.SetStencilTestMask(27)
render.SetStencilFailOperation(STENCILOPERATION_KEEP)
render.SetStencilZFailOperation(STENCILOPERATION_KEEP)
render.SetStencilPassOperation(STENCILOPERATION_REPLACE)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS)
render.SetStencilReferenceValue(27)
for i = 1, #ix.blurRenderQueue do
ix.blurRenderQueue[i]()
end
render.SetStencilReferenceValue(34)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL)
render.SetStencilPassOperation(STENCILOPERATION_REPLACE)
render.SetStencilReferenceValue(27)
cam.Start2D()
ix.util.DrawBlurAt(0, 0, ScrW(), ScrH())
cam.End2D()
render.SetStencilEnable(false)
ix.blurRenderQueue = {}
end
function GM:PostDrawHUD()
cam.Start2D()
ix.hud.DrawAll()
if (!IsValid(ix.gui.deathScreen) and (!IsValid(ix.gui.characterMenu) or ix.gui.characterMenu:IsClosing())) then
ix.bar.DrawAction()
end
cam.End2D()
end
function GM:ShouldPopulateEntityInfo(entity)
local client = LocalPlayer()
local ragdoll = Entity(client:GetLocalVar("ragdoll", 0))
local entityPlayer = entity:GetNetVar("player")
if (vgui.CursorVisible() or !client:Alive() or IsValid(ragdoll) or entity == client or entityPlayer == client) then
return false
end
end
local injTextTable = {
[.3] = {"injMajor", Color(192, 57, 43)},
[.6] = {"injLittle", Color(231, 76, 60)},
}
function GM:GetInjuredText(client)
local health = client:Health()
for k, v in pairs(injTextTable) do
if ((health / client:GetMaxHealth()) < k) then
return v[1], v[2]
end
end
end
function GM:PopulateImportantCharacterInfo(client, character, container)
local color = team.GetColor(client:Team())
container:SetArrowColor(color)
-- name
local name = container:AddRow("name")
name:SetImportant()
name:SetText(hookRun("GetCharacterName", client) or character:GetName())
name:SetBackgroundColor(color)
name:SizeToContents()
-- injured text
local injureText, injureTextColor = hookRun("GetInjuredText", client)
if (injureText) then
local injure = container:AddRow("injureText")
injure:SetText(L(injureText))
injure:SetBackgroundColor(injureTextColor)
injure:SizeToContents()
end
end
function GM:PopulateCharacterInfo(client, character, container)
-- description
local descriptionText = character:GetDescription()
descriptionText = (descriptionText:utf8len() > 128 and
string.format("%s...", descriptionText:utf8sub(1, 125)) or
descriptionText)
if (descriptionText != "") then
local description = container:AddRow("description")
description:SetText(descriptionText)
description:SizeToContents()
end
end
function GM:KeyRelease(client, key)
if (!IsFirstTimePredicted()) then
return
end
if (key == IN_USE) then
if (!ix.menu.IsOpen()) then
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local entity = util.TraceLine(data).Entity
if (IsValid(entity) and isfunction(entity.GetEntityMenu)) then
hook.Run("ShowEntityMenu", entity)
end
end
timer.Remove("ixItemUse")
client.ixInteractionTarget = nil
client.ixInteractionStartTime = nil
end
end
function GM:PlayerBindPress(client, bind, pressed)
bind = bind:lower()
if (bind:find("use") and pressed) then
local pickupTime = ix.config.Get("itemPickupTime", 0.5)
if (pickupTime > 0) then
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local entity = util.TraceLine(data).Entity
if (IsValid(entity) and entity.ShowPlayerInteraction and !ix.menu.IsOpen()) then
client.ixInteractionTarget = entity
client.ixInteractionStartTime = SysTime()
timer.Create("ixItemUse", pickupTime, 1, function()
client.ixInteractionTarget = nil
client.ixInteractionStartTime = nil
end)
end
end
elseif (bind:find("jump")) then
local entity = Entity(client:GetLocalVar("ragdoll", 0))
if (IsValid(entity)) then
ix.command.Send("CharGetUp")
end
elseif (bind:find("speed") and client:KeyDown(IN_WALK) and pressed) then
if (LocalPlayer():Crouching()) then
RunConsoleCommand("-duck")
else
RunConsoleCommand("+duck")
end
end
end
function GM:CreateMove(command)
if ((IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu.bClosing) or
(IsValid(ix.gui.menu) and !ix.gui.menu.bClosing and ix.gui.menu:GetActiveTab() == "you")) then
command:ClearButtons()
command:ClearMovement()
end
end
-- Called when use has been pressed on an item.
function GM:ShowEntityMenu(entity)
local options = entity:GetEntityMenu(LocalPlayer())
if (istable(options) and !table.IsEmpty(options)) then
ix.menu.Open(options, entity)
end
end
local hidden = {}
hidden["CHudHealth"] = true
hidden["CHudBattery"] = true
hidden["CHudAmmo"] = true
hidden["CHudSecondaryAmmo"] = true
hidden["CHudCrosshair"] = true
hidden["CHudHistoryResource"] = true
hidden["CHudPoisonDamageIndicator"] = true
hidden["CHudSquadStatus"] = true
hidden["CHUDQuickInfo"] = true
function GM:HUDShouldDraw(element)
if (hidden[element]) then
return false
end
return true
end
function GM:ShouldDrawLocalPlayer(client)
if (IsValid(ix.gui.characterMenu) and ix.gui.characterMenu:IsVisible()) then
return false
end
end
function GM:PostProcessPermitted(class)
return false
end
function GM:RenderScreenspaceEffects()
local menu = ix.gui.menu
if (IsValid(menu) and menu:GetCharacterOverview()) then
local client = LocalPlayer()
local target = client:GetObserverTarget()
local weapon = client:GetActiveWeapon()
cam.Start3D()
ix.util.ResetStencilValues()
render.SetStencilEnable(true)
render.SuppressEngineLighting(true)
cam.IgnoreZ(true)
render.SetColorModulation(1, 1, 1)
render.SetStencilWriteMask(28)
render.SetStencilTestMask(28)
render.SetStencilReferenceValue(28)
render.SetStencilCompareFunction(STENCIL_ALWAYS)
render.SetStencilPassOperation(STENCIL_REPLACE)
render.SetStencilFailOperation(STENCIL_KEEP)
render.SetStencilZFailOperation(STENCIL_KEEP)
if (IsValid(target)) then
target:DrawModel()
else
client:DrawModel()
end
if (IsValid(weapon)) then
weapon:DrawModel()
end
hook.Run("DrawCharacterOverview")
render.SetStencilCompareFunction(STENCIL_NOTEQUAL)
render.SetStencilPassOperation(STENCIL_KEEP)
cam.Start2D()
derma.SkinFunc("DrawCharacterStatusBackground", menu, menu.overviewFraction)
cam.End2D()
cam.IgnoreZ(false)
render.SuppressEngineLighting(false)
render.SetStencilEnable(false)
cam.End3D()
end
end
function GM:ShowPlayerOptions(client, options)
options["viewProfile"] = {"icon16/user.png", function()
if (IsValid(client)) then
client:ShowProfile()
end
end}
options["Copy Steam ID"] = {"icon16/user.png", function()
if (IsValid(client)) then
SetClipboardText(client:SteamID())
end
end}
end
function GM:DrawHelixModelView(panel, ent)
if (ent.weapon and IsValid(ent.weapon)) then
ent.weapon:DrawModel()
end
end
net.Receive("ixStringRequest", function()
local time = net.ReadUInt(32)
local title, subTitle = net.ReadString(), net.ReadString()
local default = net.ReadString()
if (title:sub(1, 1) == "@") then
title = L(title:sub(2))
end
if (subTitle:sub(1, 1) == "@") then
subTitle = L(subTitle:sub(2))
end
Derma_StringRequest(title, subTitle, default or "", function(text)
net.Start("ixStringRequest")
net.WriteUInt(time, 32)
net.WriteString(text)
net.SendToServer()
end)
end)
net.Receive("ixPlayerDeath", function()
if (IsValid(ix.gui.deathScreen)) then
ix.gui.deathScreen:Remove()
end
ix.gui.deathScreen = vgui.Create("ixDeathScreen")
end)
function GM:Think()
local client = LocalPlayer()
if (IsValid(client) and client:Alive() and client.ixRaisedTween) then
client.ixRaisedTween:update(FrameTime())
end
end
function GM:ScreenResolutionChanged(oldW, oldH)
hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont"))
if (IsValid(ix.gui.notices)) then
ix.gui.notices:Remove()
ix.gui.notices = vgui.Create("ixNoticeManager")
end
if (IsValid(ix.gui.bars)) then
ix.gui.bars:Remove()
ix.gui.bars = vgui.Create("ixInfoBarManager")
end
end
function GM:DrawDeathNotice()
return false
end
function GM:HUDAmmoPickedUp()
return false
end
function GM:HUDDrawPickupHistory()
return false
end
function GM:HUDDrawTargetID()
return false
end
function GM:BuildBusinessMenu()
if (!ix.config.Get("allowBusiness", true)) then
return false
end
end
gameevent.Listen("player_spawn")
hook.Add("player_spawn", "ixPlayerSpawn", function(data)
local client = Player(data.userid)
if (IsValid(client)) then
-- GetBoneName returns __INVALIDBONE__ for everything the first time you use it, so we'll force an update to make them valid
client:SetupBones()
client:SetIK(false)
if (client == LocalPlayer() and (IsValid(ix.gui.deathScreen) and !ix.gui.deathScreen:IsClosing())) then
ix.gui.deathScreen:Close()
end
end
end)
================================================
FILE: gamemode/core/hooks/sh_hooks.lua
================================================
function GM:PlayerNoClip(client)
return client:IsAdmin()
end
-- luacheck: globals HOLDTYPE_TRANSLATOR
HOLDTYPE_TRANSLATOR = {}
HOLDTYPE_TRANSLATOR[""] = "normal"
HOLDTYPE_TRANSLATOR["physgun"] = "smg"
HOLDTYPE_TRANSLATOR["ar2"] = "smg"
HOLDTYPE_TRANSLATOR["crossbow"] = "shotgun"
HOLDTYPE_TRANSLATOR["rpg"] = "shotgun"
HOLDTYPE_TRANSLATOR["slam"] = "normal"
HOLDTYPE_TRANSLATOR["grenade"] = "grenade"
HOLDTYPE_TRANSLATOR["fist"] = "normal"
HOLDTYPE_TRANSLATOR["melee2"] = "melee"
HOLDTYPE_TRANSLATOR["passive"] = "normal"
HOLDTYPE_TRANSLATOR["knife"] = "melee"
HOLDTYPE_TRANSLATOR["duel"] = "pistol"
HOLDTYPE_TRANSLATOR["camera"] = "smg"
HOLDTYPE_TRANSLATOR["magic"] = "normal"
HOLDTYPE_TRANSLATOR["revolver"] = "pistol"
-- luacheck: globals PLAYER_HOLDTYPE_TRANSLATOR
PLAYER_HOLDTYPE_TRANSLATOR = {}
PLAYER_HOLDTYPE_TRANSLATOR[""] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["fist"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["pistol"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["grenade"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["melee"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["slam"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["melee2"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["passive"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["knife"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["duel"] = "normal"
PLAYER_HOLDTYPE_TRANSLATOR["bugbait"] = "normal"
local PLAYER_HOLDTYPE_TRANSLATOR = PLAYER_HOLDTYPE_TRANSLATOR
local HOLDTYPE_TRANSLATOR = HOLDTYPE_TRANSLATOR
local animationFixOffset = Vector(16.5438, -0.1642, -20.5493)
function GM:TranslateActivity(client, act)
local clientInfo = client:GetTable()
local modelClass = clientInfo.ixAnimModelClass or "player"
local bRaised = client:IsWepRaised()
if (modelClass == "player") then
local weapon = client:GetActiveWeapon()
local bAlwaysRaised = ix.config.Get("weaponAlwaysRaised")
weapon = IsValid(weapon) and weapon or nil
if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then
local model = string.lower(client:GetModel())
if (string.find(model, "zombie")) then
local tree = ix.anim.zombie
if (string.find(model, "fast")) then
tree = ix.anim.fastZombie
end
if (tree[act]) then
return tree[act]
end
end
local holdType = weapon and (weapon.HoldType or weapon:GetHoldType()) or "normal"
if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then
holdType = PLAYER_HOLDTYPE_TRANSLATOR[holdType] or "passive"
end
local tree = ix.anim.player[holdType]
if (tree and tree[act]) then
if (isstring(tree[act])) then
clientInfo.CalcSeqOverride = client:LookupSequence(tree[act])
return
else
return tree[act]
end
end
end
return self.BaseClass:TranslateActivity(client, act)
end
if (clientInfo.ixAnimTable) then
local glide = clientInfo.ixAnimGlide
if (client:InVehicle()) then
act = clientInfo.ixAnimTable[1]
local fixVector = clientInfo.ixAnimTable[2]
if (isvector(fixVector)) then
client:SetLocalPos(animationFixOffset)
end
if (isstring(act)) then
clientInfo.CalcSeqOverride = client:LookupSequence(act)
else
return act
end
elseif (client:OnGround()) then
if (clientInfo.ixAnimTable[act]) then
local act2 = clientInfo.ixAnimTable[act][bRaised and 2 or 1]
if (isstring(act2)) then
clientInfo.CalcSeqOverride = client:LookupSequence(act2)
else
return act2
end
end
elseif (glide) then
if (isstring(glide)) then
clientInfo.CalcSeqOverride = client:LookupSequence(glide)
else
return clientInfo.ixAnimGlide
end
end
end
end
function GM:CanPlayerUseBusiness(client, uniqueID)
if (!ix.config.Get("allowBusiness", true)) then
return false
end
local itemTable = ix.item.list[uniqueID]
if (!client:GetCharacter()) then
return false
end
if (itemTable.noBusiness) then
return false
end
if (itemTable.factions) then
local allowed = false
if (istable(itemTable.factions)) then
for _, v in pairs(itemTable.factions) do
if (client:Team() == v) then
allowed = true
break
end
end
elseif (client:Team() != itemTable.factions) then
allowed = false
end
if (!allowed) then
return false
end
end
if (itemTable.classes) then
local allowed = false
if (istable(itemTable.classes)) then
for _, v in pairs(itemTable.classes) do
if (client:GetCharacter():GetClass() == v) then
allowed = true
break
end
end
elseif (client:GetCharacter():GetClass() == itemTable.classes) then
allowed = true
end
if (!allowed) then
return false
end
end
if (itemTable.flag) then
if (!client:GetCharacter():HasFlags(itemTable.flag)) then
return false
end
end
return true
end
function GM:DoAnimationEvent(client, event, data)
local class = client.ixAnimModelClass
if (class == "player") then
return self.BaseClass:DoAnimationEvent(client, event, data)
else
local weapon = client:GetActiveWeapon()
if (IsValid(weapon)) then
local animation = client.ixAnimTable
if (event == PLAYERANIMEVENT_ATTACK_PRIMARY) then
client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true)
return ACT_VM_PRIMARYATTACK
elseif (event == PLAYERANIMEVENT_ATTACK_SECONDARY) then
client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true)
return ACT_VM_SECONDARYATTACK
elseif (event == PLAYERANIMEVENT_RELOAD) then
client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.reload or ACT_GESTURE_RELOAD_SMG1, true)
return ACT_INVALID
elseif (event == PLAYERANIMEVENT_JUMP) then
client:AnimRestartMainSequence()
return ACT_INVALID
elseif (event == PLAYERANIMEVENT_CANCEL_RELOAD) then
client:AnimResetGestureSlot(GESTURE_SLOT_ATTACK_AND_RELOAD)
return ACT_INVALID
end
end
end
return ACT_INVALID
end
function GM:EntityEmitSound(data)
if (data.Entity.ixIsMuted) then
return false
end
end
function GM:EntityRemoved(entity)
if (SERVER) then
entity:ClearNetVars()
elseif (entity:IsWeapon()) then
local owner = entity:GetOwner()
-- GetActiveWeapon is the player's new weapon at this point so we'll assume
-- that the player switched away from this weapon
if (IsValid(owner) and owner:IsPlayer()) then
hook.Run("PlayerWeaponChanged", owner, owner:GetActiveWeapon())
end
end
end
local function UpdatePlayerHoldType(client, weapon)
weapon = weapon or client:GetActiveWeapon()
local holdType = "normal"
if (IsValid(weapon)) then
holdType = weapon.HoldType or weapon:GetHoldType()
holdType = HOLDTYPE_TRANSLATOR[holdType] or holdType
end
client.ixAnimHoldType = holdType
end
local function UpdateAnimationTable(client, vehicle)
local baseTable = ix.anim[client.ixAnimModelClass] or {}
if (IsValid(client) and IsValid(vehicle)) then
local vehicleClass = vehicle:IsChair() and "chair" or vehicle:GetClass()
if (baseTable.vehicle and baseTable.vehicle[vehicleClass]) then
client.ixAnimTable = baseTable.vehicle[vehicleClass]
else
client.ixAnimTable = baseTable.normal[ACT_MP_CROUCH_IDLE]
end
else
client.ixAnimTable = baseTable[client.ixAnimHoldType]
end
client.ixAnimGlide = baseTable["glide"]
end
function GM:PlayerWeaponChanged(client, weapon)
UpdatePlayerHoldType(client, weapon)
UpdateAnimationTable(client)
if (CLIENT) then
return
end
-- update weapon raise state
if (weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()]) then
client:SetWepRaised(true, weapon)
return
elseif (weapon.IsAlwaysLowered or weapon.NeverRaised) then
client:SetWepRaised(false, weapon)
return
end
-- If the player has been forced to have their weapon lowered.
if (client:IsRestricted()) then
client:SetWepRaised(false, weapon)
return
end
-- Let the config decide before actual results.
if (ix.config.Get("weaponAlwaysRaised")) then
client:SetWepRaised(true, weapon)
return
end
client:SetWepRaised(false, weapon)
end
function GM:PlayerSwitchWeapon(client, oldWeapon, weapon)
if (!IsFirstTimePredicted()) then
return
end
-- the player switched weapon themself (i.e not through SelectWeapon), so we have to network it here
if (SERVER) then
net.Start("PlayerSelectWeapon")
net.WriteEntity(client)
net.WriteString(weapon:GetClass())
net.Broadcast()
end
hook.Run("PlayerWeaponChanged", client, weapon)
end
function GM:PlayerModelChanged(client, model)
client.ixAnimModelClass = ix.anim.GetModelClass(model)
UpdateAnimationTable(client)
end
do
local vectorAngle = FindMetaTable("Vector").Angle
local normalizeAngle = math.NormalizeAngle
function GM:CalcMainActivity(client, velocity)
local clientInfo = client:GetTable()
local forcedSequence = client:GetNetVar("forcedSequence")
if (forcedSequence) then
if (client:GetSequence() != forcedSequence) then
client:SetCycle(0)
end
return -1, forcedSequence
end
client:SetPoseParameter("move_yaw", normalizeAngle(vectorAngle(velocity)[2] - client:EyeAngles()[2]))
local sequenceOverride = clientInfo.CalcSeqOverride
clientInfo.CalcSeqOverride = -1
clientInfo.CalcIdeal = ACT_MP_STAND_IDLE
-- we could call the baseclass function, but it's faster to do it this way
local BaseClass = self.BaseClass
if (BaseClass:HandlePlayerNoClipping(client, velocity) or
BaseClass:HandlePlayerDriving(client) or
BaseClass:HandlePlayerVaulting(client, velocity) or
BaseClass:HandlePlayerJumping(client, velocity) or
BaseClass:HandlePlayerSwimming(client, velocity) or
BaseClass:HandlePlayerDucking(client, velocity)) then -- luacheck: ignore 542
else
local length = velocity:Length2DSqr()
if (length > 22500) then
clientInfo.CalcIdeal = ACT_MP_RUN
elseif (length > 0.25) then
clientInfo.CalcIdeal = ACT_MP_WALK
end
end
clientInfo.m_bWasOnGround = client:OnGround()
clientInfo.m_bWasNoclipping = (client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle())
return clientInfo.CalcIdeal, sequenceOverride or clientInfo.CalcSeqOverride or -1
end
end
do
local KEY_BLACKLIST = bit.bor( IN_ATTACK, IN_ATTACK2 )
function GM:StartCommand(client, command)
if (!client:CanShootWeapon()) then
command:RemoveKey(KEY_BLACKLIST)
end
end
end
function GM:CharacterVarChanged(char, varName, oldVar, newVar)
if (ix.char.varHooks[varName]) then
for _, v in pairs(ix.char.varHooks[varName]) do
v(char, oldVar, newVar)
end
end
end
function GM:CanPlayerThrowPunch(client)
if (!client:IsWepRaised()) then
return false
end
return true
end
function GM:OnCharacterCreated(client, character)
local faction = ix.faction.Get(character:GetFaction())
if (faction and faction.OnCharacterCreated) then
faction:OnCharacterCreated(client, character)
end
end
function GM:GetDefaultCharacterName(client, faction)
local info = ix.faction.indices[faction]
if (info and info.GetDefaultName) then
return info:GetDefaultName(client)
end
end
function GM:CanPlayerUseCharacter(client, character)
local banned = character:GetData("banned")
if (banned) then
if (!isnumber(banned)) then
return false, "@charBanned"
else
if (banned > os.time()) then
return false, "@charBannedTemp"
end
end
end
local bHasWhitelist = client:HasWhitelist(character:GetFaction())
if (!bHasWhitelist) then
return false, "@noWhitelist"
end
end
function GM:CanProperty(client, property, entity)
if (client:IsAdmin()) then
return true
end
if (CLIENT and (property == "remover" or property == "collision")) then
return true
end
return false
end
function GM:PhysgunPickup(client, entity)
local bPickup = self.BaseClass:PhysgunPickup(client, entity)
if (!bPickup and entity:IsPlayer() and (client:IsSuperAdmin() or client:IsAdmin() and !entity:IsSuperAdmin())) then
bPickup = true
end
if (bPickup) then
if (entity:IsPlayer()) then
entity:SetMoveType(MOVETYPE_NONE)
elseif (!entity.ixCollisionGroup) then
entity.ixCollisionGroup = entity:GetCollisionGroup()
entity:SetCollisionGroup(COLLISION_GROUP_WEAPON)
end
end
return bPickup
end
function GM:PhysgunDrop(client, entity)
if (entity:IsPlayer()) then
entity:SetMoveType(MOVETYPE_WALK)
elseif (entity.ixCollisionGroup) then
entity:SetCollisionGroup(entity.ixCollisionGroup)
entity.ixCollisionGroup = nil
end
end
do
local TOOL_DANGEROUS = {}
TOOL_DANGEROUS["dynamite"] = true
TOOL_DANGEROUS["duplicator"] = true
function GM:CanTool(client, trace, tool)
if (client:IsAdmin()) then
return true
end
if (TOOL_DANGEROUS[tool]) then
return false
end
return self.BaseClass:CanTool(client, trace, tool)
end
end
function GM:Move(client, moveData)
local char = client:GetCharacter()
if (char) then
if (client:GetNetVar("actEnterAngle")) then
moveData:SetForwardSpeed(0)
moveData:SetSideSpeed(0)
moveData:SetVelocity(vector_origin)
end
if (client:GetMoveType() == MOVETYPE_WALK and moveData:KeyDown(IN_WALK)) then
local mf, ms = 0, 0
local speed = client:GetWalkSpeed()
local ratio = ix.config.Get("walkRatio")
if (moveData:KeyDown(IN_FORWARD)) then
mf = ratio
elseif (moveData:KeyDown(IN_BACK)) then
mf = -ratio
end
if (moveData:KeyDown(IN_MOVELEFT)) then
ms = -ratio
elseif (moveData:KeyDown(IN_MOVERIGHT)) then
ms = ratio
end
moveData:SetForwardSpeed(mf * speed)
moveData:SetSideSpeed(ms * speed)
end
end
end
function GM:CanTransferItem(itemObject, curInv, inventory)
if (SERVER) then
local client = itemObject.GetOwner and itemObject:GetOwner() or nil
if (IsValid(client) and curInv.GetReceivers) then
local bAuthorized = false
for _, v in ipairs(curInv:GetReceivers()) do
if (client == v) then
bAuthorized = true
break
end
end
if (!bAuthorized) then
return false
end
end
end
-- we can transfer anything that isn't a bag
if (!itemObject or !itemObject.isBag) then
return
end
-- don't allow bags to be put inside bags
if (inventory.id != 0 and curInv.id != inventory.id) then
if (inventory.vars and inventory.vars.isBag) then
local owner = itemObject:GetOwner()
if (IsValid(owner)) then
owner:NotifyLocalized("nestedBags")
end
return false
end
elseif (inventory.id != 0 and curInv.id == inventory.id) then
-- we are simply moving items around if we're transferring to the same inventory
return
end
inventory = ix.item.inventories[itemObject:GetData("id")]
-- don't allow transferring items that are in use
if (inventory) then
for k, _ in inventory:Iter() do
if (k:GetData("equip") == true) then
local owner = itemObject:GetOwner()
if (owner and IsValid(owner)) then
owner:NotifyLocalized("equippedBag")
end
return false
end
end
end
end
function GM:CanPlayerEquipItem(client, item)
return item.invID == client:GetCharacter():GetInventory():GetID()
end
function GM:CanPlayerUnequipItem(client, item)
return item.invID == client:GetCharacter():GetInventory():GetID()
end
function GM:OnItemTransferred(item, curInv, inventory)
local bagInventory = item.GetInventory and item:GetInventory()
if (!bagInventory) then
return
end
-- we need to retain the receiver if the owner changed while viewing as storage
if (inventory.storageInfo and isfunction(curInv.GetOwner)) then
bagInventory:AddReceiver(curInv:GetOwner())
end
end
function GM:ShowHelp() end
function GM:PreGamemodeLoaded()
hook.Remove("PostDrawEffects", "RenderWidgets")
hook.Remove("PlayerTick", "TickWidgets")
hook.Remove("RenderScene", "RenderStereoscopy")
end
function GM:PostGamemodeLoaded()
baseclass.Set("ix_character", ix.meta.character)
baseclass.Set("ix_inventory", ix.meta.inventory)
baseclass.Set("ix_item", ix.meta.item)
end
if (SERVER) then
util.AddNetworkString("PlayerVehicle")
function GM:PlayerEnteredVehicle(client, vehicle, role)
UpdateAnimationTable(client)
net.Start("PlayerVehicle")
net.WriteEntity(client)
net.WriteEntity(vehicle)
net.WriteBool(true)
net.Broadcast()
end
function GM:PlayerLeaveVehicle(client, vehicle)
UpdateAnimationTable(client)
net.Start("PlayerVehicle")
net.WriteEntity(client)
net.WriteEntity(vehicle)
net.WriteBool(false)
net.Broadcast()
end
else
net.Receive("PlayerVehicle", function(length)
local client = net.ReadEntity()
local vehicle = net.ReadEntity()
local bEntered = net.ReadBool()
UpdateAnimationTable(client, bEntered and vehicle or false)
end)
end
================================================
FILE: gamemode/core/hooks/sv_hooks.lua
================================================
util.AddNetworkString("ixPlayerDeath")
function GM:PlayerInitialSpawn(client)
client.ixJoinTime = RealTime()
if (client:IsBot()) then
local botID = os.time() + client:EntIndex()
local index = math.random(1, table.Count(ix.faction.indices))
local faction = ix.faction.indices[index]
local models = faction:GetModels( client )
local model = models[ math.random( #models ) ]
if ( istable( model ) ) then model = model[ 1 ] end
if ( !isstring( model ) ) then model = "models/gman.mdl" end
local character = ix.char.New({
name = client:Name(),
faction = faction and faction.uniqueID or "unknown",
model = model,
}, botID, client, client:SteamID64())
character.isBot = true
local inventory = ix.inventory.Create(ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight"), botID)
inventory:SetOwner(botID)
inventory.noSave = true
character.vars.inv = {inventory}
ix.char.loaded[botID] = character
character:Setup()
client:Spawn()
ix.chat.Send(nil, "connect", client:SteamName())
return
end
ix.config.Send(client)
ix.date.Send(client)
client:LoadData(function(data)
if (!IsValid(client)) then return end
-- Don't use the character cache if they've connected to another server using the same database
local address = ix.util.GetAddress()
local bNoCache = client:GetData("lastIP", address) != address
client:SetData("lastIP", address)
net.Start("ixDataSync")
net.WriteTable(data or {})
net.WriteUInt(client.ixPlayTime or 0, 32)
net.Send(client)
ix.char.Restore(client, function(charList)
if (!IsValid(client)) then return end
MsgN("Loaded (" .. table.concat(charList, ", ") .. ") for " .. client:Name())
for _, v in ipairs(charList) do
ix.char.loaded[v]:Sync(client)
end
client.ixCharList = charList
net.Start("ixCharacterMenu")
net.WriteUInt(#charList, 6)
for _, v in ipairs(charList) do
net.WriteUInt(v, 32)
end
net.Send(client)
client.ixLoaded = true
client:SetData("intro", true)
for _, v in player.Iterator() do
if (v:GetCharacter()) then
v:GetCharacter():Sync(client)
end
end
end, bNoCache)
ix.chat.Send(nil, "connect", client:SteamName())
end)
client:SetNoDraw(true)
client:SetNotSolid(true)
client:Lock()
client:SyncVars()
timer.Simple(1, function()
if (!IsValid(client)) then
return
end
client:KillSilent()
client:StripAmmo()
end)
end
function GM:PlayerUse(client, entity)
if (client:IsRestricted() or (isfunction(entity.GetEntityMenu) and entity:GetClass() != "ix_item")) then
return false
end
return true
end
function GM:KeyPress(client, key)
if (key == IN_RELOAD) then
timer.Create("ixToggleRaise"..client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function()
if (IsValid(client)) then
client:ToggleWepRaised()
end
end)
elseif (key == IN_USE) then
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local entity = util.TraceLine(data).Entity
if (IsValid(entity) and hook.Run("PlayerUse", client, entity)) then
if (entity:IsDoor()) then
local result = hook.Run("CanPlayerUseDoor", client, entity)
if (result != false) then
hook.Run("PlayerUseDoor", client, entity)
end
end
end
end
end
function GM:KeyRelease(client, key)
if (key == IN_RELOAD) then
timer.Remove("ixToggleRaise" .. client:SteamID())
elseif (key == IN_USE) then
timer.Remove("ixCharacterInteraction" .. client:SteamID())
end
end
function GM:CanPlayerInteractEntity(client, entity, option, data)
return entity:GetPos():DistToSqr(client:GetPos()) <= 96 ^ 2
end
function GM:CanPlayerInteractItem(client, action, item, data)
if (client:IsRestricted()) then
return false
end
if (IsValid(client.ixRagdoll)) then
client:NotifyLocalized("notNow")
return false
end
if (action == "drop" and hook.Run("CanPlayerDropItem", client, item) == false) then
return false
end
if (action == "take" and hook.Run("CanPlayerTakeItem", client, item) == false) then
return false
end
if (action == "combine") then
local other = data[1]
if (hook.Run("CanPlayerCombineItem", client, item, other) == false) then
return false
end
local combineItem = ix.item.instances[other]
if (combineItem and combineItem.invID != 0) then
local combineInv = ix.item.inventories[combineItem.invID]
if (!combineInv:OnCheckAccess(client)) then
return false
end
else
return false
end
end
if (isentity(item) and item.ixSteamID and item.ixCharID
and item.ixSteamID == client:SteamID() and item.ixCharID != client:GetCharacter():GetID()
and !item:GetItemTable().bAllowMultiCharacterInteraction) then
client:NotifyLocalized("itemOwned")
return false
end
return client:Alive()
end
function GM:CanPlayerDropItem(client, item)
end
function GM:CanPlayerTakeItem(client, item)
end
function GM:CanPlayerCombineItem(client, item, other)
end
function GM:PlayerShouldTakeDamage(client, attacker)
return client:GetCharacter() != nil
end
function GM:GetFallDamage(client, speed)
return (speed - 580) * (100 / 444)
end
function GM:EntityTakeDamage(entity, dmgInfo)
local inflictor = dmgInfo:GetInflictor()
if (IsValid(inflictor) and inflictor:GetClass() == "ix_item") then
dmgInfo:SetDamage(0)
return
end
if (IsValid(entity.ixPlayer)) then
if (IsValid(entity.ixHeldOwner)) then
dmgInfo:SetDamage(0)
return
end
if (dmgInfo:IsDamageType(DMG_CRUSH)) then
if ((entity.ixFallGrace or 0) < CurTime()) then
if (dmgInfo:GetDamage() <= 10) then
dmgInfo:SetDamage(0)
end
entity.ixFallGrace = CurTime() + 0.5
else
return
end
end
entity.ixPlayer:TakeDamageInfo(dmgInfo)
end
end
function GM:PrePlayerLoadedCharacter(client, character, lastChar)
-- Reset all bodygroups
client:ResetBodygroups()
-- Remove all skins
client:SetSkin(0)
end
function GM:PlayerLoadedCharacter(client, character, lastChar)
local query = mysql:Update("ix_characters")
query:Where("id", character:GetID())
query:Update("last_join_time", math.floor(os.time()))
query:Execute()
if (lastChar) then
local charEnts = lastChar:GetVar("charEnts") or {}
for _, v in ipairs(charEnts) do
if (v and IsValid(v)) then
v:Remove()
end
end
lastChar:SetVar("charEnts", nil)
end
if (character) then
for _, v in pairs(ix.class.list) do
if (v.faction == client:Team() and v.isDefault) then
character:SetClass(v.index)
break
end
end
end
if (IsValid(client.ixRagdoll)) then
client.ixRagdoll.ixNoReset = true
client.ixRagdoll.ixIgnoreDelete = true
client.ixRagdoll:Remove()
end
local faction = ix.faction.indices[character:GetFaction()]
local uniqueID = "ixSalary" .. client:SteamID64()
if (faction and faction.pay and faction.pay > 0) then
timer.Create(uniqueID, faction.payTime or 300, 0, function()
if (IsValid(client)) then
if (hook.Run("CanPlayerEarnSalary", client, faction) != false) then
local pay = hook.Run("GetSalaryAmount", client, faction) or faction.pay
character:GiveMoney(pay)
client:NotifyLocalized("salary", ix.currency.Get(pay))
end
else
timer.Remove(uniqueID)
end
end)
elseif (timer.Exists(uniqueID)) then
timer.Remove(uniqueID)
end
hook.Run("PlayerLoadout", client)
end
function GM:CharacterLoaded(character)
local client = character:GetPlayer()
if (IsValid(client)) then
local uniqueID = "ixSaveChar"..client:SteamID()
timer.Create(uniqueID, ix.config.Get("saveInterval"), 0, function()
if (IsValid(client) and client:GetCharacter()) then
client:GetCharacter():Save()
else
timer.Remove(uniqueID)
end
end)
end
end
function GM:PlayerSay(client, text)
local chatType, message, anonymous = ix.chat.Parse(client, text, true)
if (chatType == "ic") then
if (ix.command.Parse(client, message)) then
return ""
end
end
text = ix.chat.Send(client, chatType, message, anonymous)
if (isstring(text) and chatType != "ic") then
ix.log.Add(client, "chat", chatType and chatType:utf8upper() or "??", text)
end
hook.Run("PostPlayerSay", client, chatType, message, anonymous)
return ""
end
function GM:CanAutoFormatMessage(client, chatType, message)
return chatType == "ic" or chatType == "w" or chatType == "y"
end
function GM:PlayerSpawn(client)
client:SetNoDraw(false)
client:UnLock()
client:SetNotSolid(false)
client:SetMoveType(MOVETYPE_WALK)
client:SetRagdolled(false)
client:SetAction()
client:SetDSP(1)
hook.Run("PlayerLoadout", client)
end
-- Shortcuts for (super)admin only things.
local function IsAdmin(_, client)
return client:IsAdmin()
end
-- Set the gamemode hooks to the appropriate shortcuts.
GM.PlayerGiveSWEP = IsAdmin
GM.PlayerSpawnEffect = IsAdmin
GM.PlayerSpawnSENT = IsAdmin
function GM:PlayerSpawnNPC(client, npcType, weapon)
return client:IsAdmin() or client:GetCharacter():HasFlags("n")
end
function GM:PlayerSpawnSWEP(client, weapon, info)
return client:IsAdmin()
end
function GM:PlayerSpawnProp(client)
if (client:GetCharacter() and client:GetCharacter():HasFlags("e")) then
return true
end
return false
end
function GM:PlayerSpawnRagdoll(client)
if (client:GetCharacter() and client:GetCharacter():HasFlags("r")) then
return true
end
return false
end
function GM:PlayerSpawnVehicle(client, model, name, data)
if (client:GetCharacter()) then
if (data.Category == "Chairs") then
return client:GetCharacter():HasFlags("c")
else
return client:GetCharacter():HasFlags("C")
end
end
return false
end
function GM:PlayerSpawnedEffect(client, model, entity)
entity:SetNetVar("owner", client:GetCharacter():GetID())
end
function GM:PlayerSpawnedNPC(client, entity)
entity:SetNetVar("owner", client:GetCharacter():GetID())
end
function GM:PlayerSpawnedProp(client, model, entity)
entity:SetNetVar("owner", client:GetCharacter():GetID())
end
function GM:PlayerSpawnedRagdoll(client, model, entity)
entity:SetNetVar("owner", client:GetCharacter():GetID())
end
function GM:PlayerSpawnedSENT(client, entity)
entity:SetNetVar("owner", client:GetCharacter():GetID())
end
function GM:PlayerSpawnedSWEP(client, entity)
entity:SetNetVar("owner", client:GetCharacter():GetID())
end
function GM:PlayerSpawnedVehicle(client, entity)
entity:SetNetVar("owner", client:GetCharacter():GetID())
end
ix.allowedHoldableClasses = {
["ix_item"] = true,
["ix_money"] = true,
["ix_shipment"] = true,
["prop_physics"] = true,
["prop_physics_override"] = true,
["prop_physics_multiplayer"] = true,
["prop_ragdoll"] = true
}
function GM:CanPlayerHoldObject(client, entity)
if (ix.allowedHoldableClasses[entity:GetClass()]) then
return true
end
end
local voiceDistance = 360000
local function CalcPlayerCanHearPlayersVoice(listener)
if (!IsValid(listener)) then
return
end
listener.ixVoiceHear = listener.ixVoiceHear or {}
local eyePos = listener:EyePos()
for _, speaker in player.Iterator() do
local speakerEyePos = speaker:EyePos()
listener.ixVoiceHear[speaker] = eyePos:DistToSqr(speakerEyePos) < voiceDistance
end
end
function GM:InitializedConfig()
ix.date.Initialize()
voiceDistance = ix.config.Get("voiceDistance")
voiceDistance = voiceDistance * voiceDistance
end
function GM:VoiceToggled(bAllowVoice)
for _, v in player.Iterator() do
local uniqueID = v:SteamID64() .. "ixCanHearPlayersVoice"
if (bAllowVoice) then
timer.Create(uniqueID, 0.5, 0, function()
CalcPlayerCanHearPlayersVoice(v)
end)
else
timer.Remove(uniqueID)
v.ixVoiceHear = nil
end
end
end
function GM:VoiceDistanceChanged(distance)
voiceDistance = distance * distance
end
-- Called when weapons should be given to a player.
function GM:PlayerLoadout(client)
if (client.ixSkipLoadout) then
client.ixSkipLoadout = nil
return
end
client:SetWeaponColor(Vector(client:GetInfo("cl_weaponcolor")))
client:StripWeapons()
client:StripAmmo()
client:SetLocalVar("blur", nil)
local character = client:GetCharacter()
-- Check if they have loaded a character.
if (character) then
client:SetupHands()
-- Set their player model to the character's model.
client:SetModel(character:GetModel())
client:Give("ix_hands")
client:SetWalkSpeed(ix.config.Get("walkSpeed"))
client:SetRunSpeed(ix.config.Get("runSpeed"))
client:SetHealth(character:GetData("health", client:GetMaxHealth()))
local faction = ix.faction.indices[client:Team()]
if (faction) then
-- If their faction wants to do something when the player spawns, let it.
if (faction.OnSpawn) then
faction:OnSpawn(client)
end
-- @todo add docs for player:Give() failing if player already has weapon - which means if a player is given a weapon
-- here due to the faction weapons table, the weapon's :Give call in the weapon base will fail since the player
-- will already have it by then. This will cause issues for weapons that have pac data since the parts are applied
-- only if the weapon returned by :Give() is valid
-- If the faction has default weapons, give them to the player.
if (faction.weapons) then
for _, v in ipairs(faction.weapons) do
client:Give(v)
end
end
end
-- Ditto, but for classes.
local class = ix.class.list[client:GetCharacter():GetClass()]
if (class) then
if (class.OnSpawn) then
class:OnSpawn(client)
end
if (class.weapons) then
for _, v in ipairs(class.weapons) do
client:Give(v)
end
end
end
-- Apply any flags as needed.
ix.flag.OnSpawn(client)
ix.attributes.Setup(client)
hook.Run("PostPlayerLoadout", client)
client:SelectWeapon("ix_hands")
else
client:SetNoDraw(true)
client:Lock()
client:SetNotSolid(true)
end
end
function GM:PostPlayerLoadout(client)
-- Reload All Attrib Boosts
local character = client:GetCharacter()
if (character:GetInventory()) then
for k, _ in character:GetInventory():Iter() do
k:Call("OnLoadout", client)
if (k:GetData("equip") and k.attribBoosts) then
for attribKey, attribValue in pairs(k.attribBoosts) do
character:AddBoost(k.uniqueID, attribKey, attribValue)
end
end
end
end
if (ix.config.Get("allowVoice")) then
timer.Create(client:SteamID64() .. "ixCanHearPlayersVoice", 0.5, 0, function()
CalcPlayerCanHearPlayersVoice(client)
end)
end
end
local deathSounds = {
Sound("vo/npc/male01/pain07.wav"),
Sound("vo/npc/male01/pain08.wav"),
Sound("vo/npc/male01/pain09.wav")
}
function GM:DoPlayerDeath(client, attacker, damageinfo)
client:AddDeaths(1)
if (hook.Run("ShouldSpawnClientRagdoll", client) != false) then
client:CreateRagdoll()
end
if (IsValid(attacker) and attacker:IsPlayer()) then
if (client == attacker) then
attacker:AddFrags(-1)
else
attacker:AddFrags(1)
end
end
net.Start("ixPlayerDeath")
net.Send(client)
client:SetAction("@respawning", ix.config.Get("spawnTime", 5))
client:SetDSP(31)
end
function GM:PlayerDeath(client, inflictor, attacker)
local character = client:GetCharacter()
if (character) then
if (IsValid(client.ixRagdoll)) then
client.ixRagdoll.ixIgnoreDelete = true
client:SetLocalVar("blur", nil)
if (hook.Run("ShouldRemoveRagdollOnDeath", client) != false) then
client.ixRagdoll:Remove()
end
end
client:SetNetVar("deathStartTime", CurTime())
client:SetNetVar("deathTime", CurTime() + ix.config.Get("spawnTime", 5))
character:SetData("health", nil)
local deathSound = hook.Run("GetPlayerDeathSound", client)
if (deathSound != false) then
deathSound = deathSound or deathSounds[math.random(1, #deathSounds)]
if (client:IsFemale() and !deathSound:find("female")) then
deathSound = deathSound:gsub("male", "female")
end
client:EmitSound(deathSound)
end
local weapon = attacker:IsPlayer() and attacker:GetActiveWeapon()
ix.log.Add(client, "playerDeath",
attacker:GetName() ~= "" and attacker:GetName() or attacker:GetClass(), IsValid(weapon) and weapon:GetClass())
end
end
local painSounds = {
Sound("vo/npc/male01/pain01.wav"),
Sound("vo/npc/male01/pain02.wav"),
Sound("vo/npc/male01/pain03.wav"),
Sound("vo/npc/male01/pain04.wav"),
Sound("vo/npc/male01/pain05.wav"),
Sound("vo/npc/male01/pain06.wav")
}
local drownSounds = {
Sound("player/pl_drown1.wav"),
Sound("player/pl_drown2.wav"),
Sound("player/pl_drown3.wav"),
}
function GM:GetPlayerPainSound(client)
if (client:WaterLevel() >= 3) then
return drownSounds[math.random(1, #drownSounds)]
end
end
function GM:PlayerHurt(client, attacker, health, damage)
if ((client.ixNextPain or 0) < CurTime() and health > 0) then
local painSound = hook.Run("GetPlayerPainSound", client) or painSounds[math.random(1, #painSounds)]
if (client:IsFemale() and !painSound:find("female")) then
painSound = painSound:gsub("male", "female")
end
client:EmitSound(painSound)
client.ixNextPain = CurTime() + 0.33
end
ix.log.Add(client, "playerHurt", damage, attacker:GetName() ~= "" and attacker:GetName() or attacker:GetClass())
end
function GM:PlayerDeathThink(client)
if (client:GetCharacter()) then
local deathTime = client:GetNetVar("deathTime")
if (deathTime and deathTime <= CurTime()) then
client:Spawn()
end
end
return false
end
function GM:PlayerDisconnected(client)
client:SaveData()
local character = client:GetCharacter()
if (character) then
local charEnts = character:GetVar("charEnts") or {}
for _, v in ipairs(charEnts) do
if (v and IsValid(v)) then
v:Remove()
end
end
hook.Run("OnCharacterDisconnect", client, character)
character:Save()
ix.chat.Send(nil, "disconnect", client:SteamName())
end
if (IsValid(client.ixRagdoll)) then
client.ixRagdoll:Remove()
end
client:ClearNetVars()
if (!client.ixVoiceHear) then
return
end
for _, v in player.Iterator() do
if (!v.ixVoiceHear) then
continue
end
v.ixVoiceHear[client] = nil
end
timer.Remove(client:SteamID64() .. "ixCanHearPlayersVoice")
end
function GM:InitPostEntity()
local doors = ents.FindByClass("prop_door_rotating")
for _, v in ipairs(doors) do
local parent = v:GetOwner()
if (IsValid(parent)) then
v.ixPartner = parent
parent.ixPartner = v
else
for _, v2 in ipairs(doors) do
if (v2:GetOwner() == v) then
v2.ixPartner = v
v.ixPartner = v2
break
end
end
end
end
timer.Simple(2, function()
ix.entityDataLoaded = true
end)
end
function GM:SaveData()
ix.date.Save()
end
function GM:ShutDown()
ix.shuttingDown = true
ix.config.Save()
hook.Run("SaveData")
for _, v in player.Iterator() do
v:SaveData()
if (v:GetCharacter()) then
v:GetCharacter():Save()
end
end
end
function GM:GetGameDescription()
return "IX: "..(Schema and Schema.name or "Unknown")
end
function GM:OnPlayerUseBusiness(client, item)
-- You can manipulate purchased items with this hook.
-- does not requires any kind of return.
-- ex) item:SetData("businessItem", true)
-- then every purchased item will be marked as Business Item.
end
function GM:PlayerDeathSound()
return true
end
function GM:InitializedSchema()
game.ConsoleCommand("sbox_persist ix_"..Schema.folder.."\n")
end
function GM:PlayerCanHearPlayersVoice(listener, speaker)
if (!speaker:Alive()) then
return false
end
local bCanHear = listener.ixVoiceHear and listener.ixVoiceHear[speaker]
return bCanHear, true
end
function GM:PlayerCanPickupWeapon(client, weapon)
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
if (trace.Entity == weapon and client:KeyDown(IN_USE)) then
return true
end
return client.ixWeaponGive
end
function GM:OnPhysgunFreeze(weapon, physObj, entity, client)
-- Validate the physObj, to prevent errors on entities who have no physics object
if (!IsValid(physObj)) then return false end
-- Object is already frozen (!?)
if (!physObj:IsMoveable()) then return false end
if (entity:GetUnFreezable()) then return false end
physObj:EnableMotion(false)
-- With the jeep we need to pause all of its physics objects
-- to stop it spazzing out and killing the server.
if (entity:GetClass() == "prop_vehicle_jeep") then
local objects = entity:GetPhysicsObjectCount()
for i = 0, objects - 1 do
entity:GetPhysicsObjectNum(i):EnableMotion(false)
end
end
-- Add it to the player's frozen props
client:AddFrozenPhysicsObject(entity, physObj)
client:SendHint("PhysgunUnfreeze", 0.3)
client:SuppressHint("PhysgunFreeze")
return true
end
function GM:CanPlayerSuicide(client)
return false
end
function GM:AllowPlayerPickup(client, entity)
return false
end
function GM:PreCleanupMap()
hook.Run("SaveData")
hook.Run("PersistenceSave")
end
function GM:PostCleanupMap()
ix.plugin.RunLoadData()
end
function GM:CharacterPreSave(character)
local client = character:GetPlayer()
for k, _ in character:GetInventory():Iter() do
if (k.OnSave) then
k:Call("OnSave", client)
end
end
character:SetData("health", client:Alive() and client:Health() or nil)
end
timer.Create("ixLifeGuard", 1, 0, function()
for _, v in player.Iterator() do
if (v:GetCharacter() and v:Alive() and hook.Run("ShouldPlayerDrowned", v) != false) then
if (v:WaterLevel() >= 3) then
if (!v.drowningTime) then
v.drowningTime = CurTime() + 30
v.nextDrowning = CurTime()
v.drownDamage = v.drownDamage or 0
end
if (v.drowningTime < CurTime()) then
if (v.nextDrowning < CurTime()) then
v:ScreenFade(1, Color(0, 0, 255, 100), 1, 0)
v:TakeDamage(10)
v.drownDamage = v.drownDamage + 10
v.nextDrowning = CurTime() + 1
end
end
else
if (v.drowningTime) then
v.drowningTime = nil
v.nextDrowning = nil
v.nextRecover = CurTime() + 2
end
if (v.nextRecover and v.nextRecover < CurTime() and v.drownDamage > 0) then
v.drownDamage = v.drownDamage - 10
v:SetHealth(math.Clamp(v:Health() + 10, 0, v:GetMaxHealth()))
v.nextRecover = CurTime() + 1
end
end
end
end
end)
net.Receive("ixStringRequest", function(length, client)
local time = net.ReadUInt(32)
local text = net.ReadString()
if (client.ixStrReqs and client.ixStrReqs[time]) then
client.ixStrReqs[time](text)
client.ixStrReqs[time] = nil
end
end)
function GM:GetPreferredCarryAngles(entity)
if (entity:GetClass() == "ix_item") then
local itemTable = entity:GetItemTable()
if (itemTable) then
local preferedAngle = itemTable.preferedAngle
if (preferedAngle) then -- I don't want to return something
return preferedAngle
end
end
end
end
function GM:PluginShouldLoad(uniqueID)
return !ix.plugin.unloaded[uniqueID]
end
function GM:DatabaseConnected()
-- Create the SQL tables if they do not exist.
ix.db.LoadTables()
ix.log.LoadTables()
MsgC(Color(0, 255, 0), "Database Type: " .. ix.db.config.adapter .. ".\n")
timer.Create("ixDatabaseThink", 0.5, 0, function()
mysql:Think()
end)
ix.plugin.RunLoadData()
end
================================================
FILE: gamemode/core/libs/cl_bar.lua
================================================
ix.bar = ix.bar or {}
ix.bar.list = {}
ix.bar.delta = ix.bar.delta or {}
ix.bar.actionText = ""
ix.bar.actionStart = 0
ix.bar.actionEnd = 0
ix.bar.totalHeight = 0
-- luacheck: globals BAR_HEIGHT
BAR_HEIGHT = 10
function ix.bar.Get(identifier)
for _, v in ipairs(ix.bar.list) do
if (v.identifier == identifier) then
return v
end
end
end
function ix.bar.Remove(identifier)
local bar = ix.bar.Get(identifier)
if (bar) then
table.remove(ix.bar.list, bar.index)
if (IsValid(ix.gui.bars)) then
ix.gui.bars:RemoveBar(bar.panel)
end
end
end
function ix.bar.Add(getValue, color, priority, identifier)
if (identifier) then
ix.bar.Remove(identifier)
end
local index = #ix.bar.list + 1
color = color or Color(math.random(150, 255), math.random(150, 255), math.random(150, 255))
priority = priority or index
ix.bar.list[index] = {
index = index,
color = color,
priority = priority,
GetValue = getValue,
identifier = identifier,
panel = IsValid(ix.gui.bars) and ix.gui.bars:AddBar(index, color, priority)
}
return priority
end
local gradientD = ix.util.GetMaterial("vgui/gradient-d")
local TEXT_COLOR = Color(240, 240, 240)
local SHADOW_COLOR = Color(20, 20, 20)
function ix.bar.DrawAction()
local start, finish = ix.bar.actionStart, ix.bar.actionEnd
local curTime = CurTime()
local scrW, scrH = ScrW(), ScrH()
if (finish > curTime) then
local fraction = 1 - math.TimeFraction(start, finish, curTime)
local alpha = fraction * 255
if (alpha > 0) then
local w, h = scrW * 0.35, 28
local x, y = (scrW * 0.5) - (w * 0.5), (scrH * 0.725) - (h * 0.5)
ix.util.DrawBlurAt(x, y, w, h)
surface.SetDrawColor(35, 35, 35, 100)
surface.DrawRect(x, y, w, h)
surface.SetDrawColor(0, 0, 0, 120)
surface.DrawOutlinedRect(x, y, w, h)
surface.SetDrawColor(ix.config.Get("color"))
surface.DrawRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8)
surface.SetDrawColor(200, 200, 200, 20)
surface.SetMaterial(gradientD)
surface.DrawTexturedRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8)
draw.SimpleText(ix.bar.actionText, "ixMediumFont", x + 2, y - 22, SHADOW_COLOR)
draw.SimpleText(ix.bar.actionText, "ixMediumFont", x, y - 24, TEXT_COLOR)
end
end
end
do
ix.bar.Add(function()
return math.max(LocalPlayer():Health() / LocalPlayer():GetMaxHealth(), 0)
end, Color(200, 50, 40), nil, "health")
ix.bar.Add(function()
return math.min(LocalPlayer():Armor() / 100, 1)
end, Color(30, 70, 180), nil, "armor")
end
net.Receive("ixActionBar", function()
local start, finish = net.ReadFloat(), net.ReadFloat()
local text = net.ReadString()
if (text:sub(1, 1) == "@") then
text = L2(text:sub(2)) or text
end
ix.bar.actionStart = start
ix.bar.actionEnd = finish
ix.bar.actionText = text:utf8upper()
end)
net.Receive("ixActionBarReset", function()
ix.bar.actionStart = 0
ix.bar.actionEnd = 0
ix.bar.actionText = ""
end)
================================================
FILE: gamemode/core/libs/cl_hud.lua
================================================
ix.hud = {}
function ix.hud.DrawItemPickup()
local pickupTime = ix.config.Get("itemPickupTime", 0.5)
if (pickupTime == 0) then
return
end
local client = LocalPlayer()
local entity = client.ixInteractionTarget
local startTime = client.ixInteractionStartTime
if (IsValid(entity) and startTime) then
local sysTime = SysTime()
local endTime = startTime + pickupTime
if (sysTime >= endTime or client:GetEyeTrace().Entity != entity) then
client.ixInteractionTarget = nil
client.ixInteractionStartTime = nil
return
end
local fraction = math.min((endTime - sysTime) / pickupTime, 1)
local x, y = ScrW() / 2, ScrH() / 2
local radius, thickness = 32, 6
local startAngle = 90
local endAngle = startAngle + (1 - fraction) * 360
local color = ColorAlpha(color_white, fraction * 255)
ix.util.DrawArc(x, y, radius, thickness, startAngle, endAngle, 2, color)
end
end
function ix.hud.PopulateItemTooltip(tooltip, item)
local name = tooltip:AddRow("name")
name:SetImportant()
name:SetText(item.GetName and item:GetName() or L(item.name))
name:SetMaxWidth(math.max(name:GetMaxWidth(), ScrW() * 0.5))
name:SizeToContents()
local description = tooltip:AddRow("description")
description:SetText(item:GetDescription() or "")
description:SizeToContents()
if (item.PopulateTooltip) then
item:PopulateTooltip(tooltip)
end
hook.Run("PopulateItemTooltip", tooltip, item)
end
function ix.hud.PopulatePlayerTooltip(tooltip, client)
local name = tooltip:AddRow("name")
name:SetImportant()
name:SetText(client:SteamName())
name:SetBackgroundColor(team.GetColor(client:Team()))
name:SizeToContents()
local nameHeight = name:GetTall()
name:SetTextInset(nameHeight + 4, 0)
name:SetWide(name:GetWide() + nameHeight + 4)
local avatar = name:Add("AvatarImage")
avatar:Dock(LEFT)
avatar:SetPlayer(client, nameHeight)
avatar:SetSize(name:GetTall(), name:GetTall())
local currentPing = client:Ping()
local ping = tooltip:AddRow("ping")
ping:SetText(L("ping", currentPing))
ping.Paint = function(_, width, height)
surface.SetDrawColor(ColorAlpha(derma.GetColor(
currentPing < 110 and "Success" or (currentPing < 165 and "Warning" or "Error")
, tooltip), 22))
surface.DrawRect(0, 0, width, height)
end
ping:SizeToContents()
hook.Run("PopulatePlayerTooltip", client, tooltip)
end
function ix.hud.DrawAll()
ix.hud.DrawItemPickup()
end
================================================
FILE: gamemode/core/libs/cl_markup.lua
================================================
-- luacheck: ignore
ix.markup = ix.markup or {}
-- Temporary information used when building text frames.
local colour_stack = { {r=255,g=255,b=255,a=255} }
local font_stack = { "DermaDefault" }
local curtag = nil
local blocks = {}
local colourmap = {
-- it's all black and white
["black"] = { r=0, g=0, b=0, a=255 },
["white"] = { r=255, g=255, b=255, a=255 },
-- it's greys
["dkgrey"] = { r=64, g=64, b=64, a=255 },
["grey"] = { r=128, g=128, b=128, a=255 },
["ltgrey"] = { r=192, g=192, b=192, a=255 },
-- account for speeling mistakes
["dkgray"] = { r=64, g=64, b=64, a=255 },
["gray"] = { r=128, g=128, b=128, a=255 },
["ltgray"] = { r=192, g=192, b=192, a=255 },
-- normal colours
["red"] = { r=255, g=0, b=0, a=255 },
["green"] = { r=0, g=255, b=0, a=255 },
["blue"] = { r=0, g=0, b=255, a=255 },
["yellow"] = { r=255, g=255, b=0, a=255 },
["purple"] = { r=255, g=0, b=255, a=255 },
["cyan"] = { r=0, g=255, b=255, a=255 },
["turq"] = { r=0, g=255, b=255, a=255 },
-- dark variations
["dkred"] = { r=128, g=0, b=0, a=255 },
["dkgreen"] = { r=0, g=128, b=0, a=255 },
["dkblue"] = { r=0, g=0, b=128, a=255 },
["dkyellow"] = { r=128, g=128, b=0, a=255 },
["dkpurple"] = { r=128, g=0, b=128, a=255 },
["dkcyan"] = { r=0, g=128, b=128, a=255 },
["dkturq"] = { r=0, g=128, b=128, a=255 },
-- light variations
["ltred"] = { r=255, g=128, b=128, a=255 },
["ltgreen"] = { r=128, g=255, b=128, a=255 },
["ltblue"] = { r=128, g=128, b=255, a=255 },
["ltyellow"] = { r=255, g=255, b=128, a=255 },
["ltpurple"] = { r=255, g=128, b=255, a=255 },
["ltcyan"] = { r=128, g=255, b=255, a=255 },
["ltturq"] = { r=128, g=255, b=255, a=255 },
}
--[[
Name: colourMatch(c)
Desc: Match a colour name to an rgb value.
Usage: ** INTERNAL ** Do not use!
]]
local function colourMatch(c)
c = string.lower(c)
return colourmap[c]
end
--[[
Name: ExtractParams(p1,p2,p3)
Desc: This function is used to extract the tag information.
Usage: ** INTERNAL ** Do not use!
]]
local function ExtractParams(p1,p2,p3)
if (string.utf8sub(p1, 1, 1) == "/") then
local tag = string.utf8sub(p1, 2)
if (tag == "color" or tag == "colour") then
table.remove(colour_stack)
elseif (tag == "font" or tag == "face") then
table.remove(font_stack)
end
else
if (p1 == "color" or p1 == "colour") then
local rgba = colourMatch(p2)
if (rgba == nil) then
rgba = {}
local x = { "r", "g", "b", "a" }
n = 1
for k, v in string.gmatch(p2, "(%d+),?") do
rgba[ x[n] ] = k
n = n + 1
end
end
table.insert(colour_stack, rgba)
elseif (p1 == "font" or p1 == "face") then
table.insert(font_stack, tostring(p2))
elseif (p1 == "img" and p2) then
local exploded = string.Explode(",", p2)
local material = exploded[1] or p2
local p3 = exploded[2]
local found = file.Find("materials/"..material..".*", "GAME")
if (found[1] and found[1]:find("%.png")) then
material = material..".png"
end
local texture = Material(material)
local sizeData = string.Explode("x", p3 or "16x16")
w = tonumber(sizeData[1]) or 16
h = tonumber(sizeData[2]) or 16
if (texture) then
table.insert(blocks, {
texture = texture,
w = w,
h = h
})
end
end
end
end
--[[
Name: CheckTextOrTag(p)
Desc: This function places data in the "blocks" table
depending of if p is a tag, or some text
Usage: ** INTERNAL ** Do not use!
]]
local function CheckTextOrTag(p)
if (p == "") then return end
if (p == nil) then return end
if (string.utf8sub(p, 1, 1) == "<") then
string.gsub(p, "<([/%a]*)=?([^>]*)", ExtractParams)
else
local text_block = {}
text_block.text = p
text_block.colour = colour_stack[#colour_stack]
text_block.font = font_stack[#font_stack]
table.insert(blocks, text_block)
end
end
--[[
Name: ProcessMatches(p1,p2,p3)
Desc: CheckTextOrTag for 3 parameters. Called by string.gsub
Usage: ** INTERNAL ** Do not use!
]]
local function ProcessMatches(p1,p2,p3)
if (p1) then CheckTextOrTag(p1) end
if (p2) then CheckTextOrTag(p2) end
if (p3) then CheckTextOrTag(p3) end
end
local MarkupObject = {}
--[[
Name: MarkupObject:Create()
Desc: Called by Parse. Creates a new table, and setups the
metatable.
Usage: ** INTERNAL ** Do not use!
]]
function MarkupObject:create()
local o = {}
setmetatable(o, self)
self.__index = self
return o
end
--[[
Name: MarkupObject:GetWidth()
Desc: Returns the width of a markup block
Usage: ml:GetWidth()
]]
function MarkupObject:GetWidth()
return self.totalWidth
end
--[[
Name: MarkupObject:GetHeight()
Desc: Returns the height of a markup block
Usage: ml:GetHeight()
]]
function MarkupObject:GetHeight()
return self.totalHeight
end
function MarkupObject:size()
return self.totalWidth, self.totalHeight
end
--[[
Name: MarkupObject:Draw(xOffset, yOffset, halign, valign, alphaoverride)
Desc: Draw the markup text to the screen as position
xOffset, yOffset. Halign and Valign can be used
to align the text. Alphaoverride can be used to override
the alpha value of the text-colour.
Usage: MarkupObject:Draw(100, 100)
]]
function MarkupObject:draw(xOffset, yOffset, halign, valign, alphaoverride)
for i = 1, #self.blocks do
local blk = self.blocks[i]
if (blk.texture) then
local y = yOffset + blk.offset.y
local x = xOffset + blk.offset.x
if (halign == TEXT_ALIGN_CENTER) then
x = x - (self.totalWidth * 0.5)
elseif (halign == TEXT_ALIGN_RIGHT) then
x = x - (self.totalWidth)
end
surface.SetDrawColor(blk.colour.r, blk.colour.g, blk.colour.b, alphaoverride or blk.colour.a or 255)
surface.SetMaterial(blk.texture)
surface.DrawTexturedRect(x, y, blk.w, blk.h)
else
local y = yOffset + (blk.height - blk.thisY) + blk.offset.y
local x = xOffset
if (halign == TEXT_ALIGN_CENTER) then x = x - (self.totalWidth / 2)
elseif (halign == TEXT_ALIGN_RIGHT) then x = x - (self.totalWidth)
end
x = x + blk.offset.x
if (self.onDrawText) then
self.onDrawText(blk.text, blk.font, x, y, blk.colour, halign, valign, alphaoverride, blk)
else
if (valign == TEXT_ALIGN_CENTER) then y = y - (self.totalHeight / 2)
elseif (valign == TEXT_ALIGN_BOTTOM) then y = y - (self.totalHeight)
end
local alpha = blk.colour.a
if (alphaoverride) then alpha = alphaoverride end
surface.SetFont( blk.font )
surface.SetTextColor( blk.colour.r, blk.colour.g, blk.colour.b, alpha )
surface.SetTextPos( x, y )
surface.DrawText( blk.text )
end
end
end
end
--[[
Name: Parse(ml, maxwidth)
Desc: Parses the pseudo-html markup language, and creates a
MarkupObject, which can be used to the draw the
text to the screen. Valid tags are: font and colour.
\n and \t are also available to move to the next line,
or insert a tab character.
Maxwidth can be used to make the text wrap to a specific
width.
Usage: markup.Parse("changed font\nchanged colour")
]]
function ix.markup.Parse(ml, maxwidth)
ml = utf8.force(ml)
colour_stack = { {r=255,g=255,b=255,a=255} }
font_stack = { "DermaDefault" }
blocks = {}
if (not string.find(ml, "<")) then
ml = ml .. ""
end
string.gsub(ml, "([^<>]*)(<[^>]+.)([^<>]*)", ProcessMatches)
local xOffset = 0
local yOffset = 0
local xSize = 0
local xMax = 0
local thisMaxY = 0
local new_block_list = {}
local ymaxes = {}
local texOffset = 0
local lineHeight = 0
for i = 1, #blocks do
local block = blocks[i]
if (block.text) then
surface.SetFont(block.font)
local thisY = 0
local curString = ""
block.text = string.gsub(block.text, ">", ">")
block.text = string.gsub(block.text, "<", "<")
block.text = string.gsub(block.text, "&", "&")
for j=1,string.utf8len(block.text) do
local ch = string.utf8sub(block.text,j,j)
if (ch == "\n") then
if (thisY == 0) then
thisY = lineHeight + texOffset;
thisMaxY = lineHeight + texOffset;
else
lineHeight = thisY + texOffset
end
if (string.utf8len(curString) > 0) then
local x1,y1 = surface.GetTextSize(curString)
local new_block = {}
new_block.text = curString
new_block.font = block.font
new_block.colour = block.colour
new_block.thisY = thisY
new_block.thisX = x1
new_block.offset = {}
new_block.offset.x = xOffset
new_block.offset.y = yOffset
table.insert(new_block_list, new_block)
if (xOffset + x1 > xMax) then
xMax = xOffset + x1
end
end
xOffset = 0
xSize = 0
yOffset = yOffset + thisMaxY;
thisY = 0
curString = ""
thisMaxY = 0
elseif (ch == "\t") then
if (string.utf8len(curString) > 0) then
local x1,y1 = surface.GetTextSize(curString)
local new_block = {}
new_block.text = curString
new_block.font = block.font
new_block.colour = block.colour
new_block.thisY = thisY
new_block.thisX = x1
new_block.offset = {}
new_block.offset.x = xOffset
new_block.offset.y = yOffset
table.insert(new_block_list, new_block)
if (xOffset + x1 > xMax) then
xMax = xOffset + x1
end
end
local xOldSize = xSize
xSize = 0
curString = ""
local xOldOffset = xOffset
xOffset = math.ceil( (xOffset + xOldSize) / 50 ) * 50
if (xOffset == xOldOffset) then
xOffset = xOffset + 50
end
else
local x,y = surface.GetTextSize(ch)
if (x == nil) then return end
if (maxwidth and maxwidth > x) then
if (xOffset + xSize + x >= maxwidth) then
-- need to: find the previous space in the curString
-- if we can't find one, take off the last character
-- and add a -. add the character to ch
-- and insert as a new block, incrementing the y etc
local lastSpacePos = string.utf8len(curString)
for k=1,string.utf8len(curString) do
local chspace = string.utf8sub(curString,k,k)
if (chspace == " ") then
lastSpacePos = k
end
end
if (lastSpacePos == string.utf8len(curString)) then
ch = string.utf8sub(curString,lastSpacePos,lastSpacePos) .. ch
j = lastSpacePos
curString = string.utf8sub(curString, 1, lastSpacePos-1)
else
ch = string.utf8sub(curString,lastSpacePos+1) .. ch
j = lastSpacePos+1
curString = string.utf8sub(curString, 1, lastSpacePos)
end
local m = 1
while string.utf8sub(ch, m, m) == " " and m <= string.utf8len(ch) do
m = m + 1
end
ch = string.utf8sub(ch, m)
local x1,y1 = surface.GetTextSize(curString)
if (y1 > thisMaxY) then thisMaxY = y1; ymaxes[yOffset] = thisMaxY; lineHeight = y1; end
local new_block = {}
new_block.text = curString
new_block.font = block.font
new_block.colour = block.colour
new_block.thisY = thisY
new_block.thisX = x1
new_block.offset = {}
new_block.offset.x = xOffset
new_block.offset.y = yOffset
table.insert(new_block_list, new_block)
if (xOffset + x1 > xMax) then
xMax = xOffset + x1
end
xOffset = 0
xSize = 0
x,y = surface.GetTextSize(ch)
yOffset = yOffset + thisMaxY;
thisY = 0
curString = ""
thisMaxY = 0
end
end
curString = curString .. ch
thisY = y
xSize = xSize + x
if (y > thisMaxY) then thisMaxY = y; ymaxes[yOffset] = thisMaxY; lineHeight = y; end
end
end
if (string.utf8len(curString) > 0) then
local x1,y1 = surface.GetTextSize(curString)
local new_block = {}
new_block.text = curString
new_block.font = block.font
new_block.colour = block.colour
new_block.thisY = thisY
new_block.thisX = x1
new_block.offset = {}
new_block.offset.x = xOffset
new_block.offset.y = yOffset
table.insert(new_block_list, new_block)
lineHeight = thisY
if (xOffset + x1 > xMax) then
xMax = xOffset + x1
end
xOffset = xOffset + x1
end
xSize = 0
elseif (block.texture) then
local newBlock = table.Copy(block)
newBlock.colour = block.colour or {r = 255, g = 255, b = 255, a = 255}
newBlock.thisX = block.w
newBlock.thisY = block.h
newBlock.offset = {
x = xOffset,
y = 0
}
table.insert(new_block_list, newBlock)
xOffset = xOffset + block.w + 1
texOffset = block.h / 2
end
end
local totalHeight = 0
for i = 1, #new_block_list do
local block = new_block_list[i]
block.height = ymaxes[block.offset.y]
if (block.texture) then
block.offset.y = ymaxes[0] * 0.5 - block.h * 0.5
end
if (block.height and block.offset.y + block.height > totalHeight) then
totalHeight = block.offset.y + block.height
end
end
local newObject = MarkupObject:create()
newObject.totalHeight = totalHeight
newObject.totalWidth = xMax
newObject.blocks = new_block_list
return newObject
end
================================================
FILE: gamemode/core/libs/cl_networking.lua
================================================
local entityMeta = FindMetaTable("Entity")
local playerMeta = FindMetaTable("Player")
ix.net = ix.net or {}
ix.net.globals = ix.net.globals or {}
net.Receive("ixGlobalVarSet", function()
ix.net.globals[net.ReadString()] = net.ReadType()
end)
net.Receive("ixNetVarSet", function()
local index = net.ReadUInt(16)
ix.net[index] = ix.net[index] or {}
ix.net[index][net.ReadString()] = net.ReadType()
end)
net.Receive("ixNetVarDelete", function()
ix.net[net.ReadUInt(16)] = nil
end)
net.Receive("ixLocalVarSet", function()
local key = net.ReadString()
local var = net.ReadType()
ix.net[LocalPlayer():EntIndex()] = ix.net[LocalPlayer():EntIndex()] or {}
ix.net[LocalPlayer():EntIndex()][key] = var
hook.Run("OnLocalVarSet", key, var)
end)
function GetNetVar(key, default) -- luacheck: globals GetNetVar
local value = ix.net.globals[key]
return value != nil and value or default
end
function entityMeta:GetNetVar(key, default)
local index = self:EntIndex()
if (ix.net[index] and ix.net[index][key] != nil) then
return ix.net[index][key]
end
return default
end
playerMeta.GetLocalVar = entityMeta.GetNetVar
================================================
FILE: gamemode/core/libs/sh_animation.lua
================================================
function ix.util.InstallAnimationMethods(meta)
local function TweenAnimationThink(object)
for k, v in pairs(object.tweenAnimations) do
if (!v.bShouldPlay) then
continue
end
local bComplete = v:update(FrameTime())
if (v.Think) then
v:Think(object)
end
if (bComplete) then
v.bShouldPlay = nil
v:ForceComplete()
if (v.OnComplete) then
v:OnComplete(object)
end
if (v.bRemoveOnComplete) then
object.tweenAnimations[k] = nil
end
end
end
end
function meta:GetTweenAnimation(index, bNoPlay)
-- if we don't need to check if the animation is playing we can just return the animation
if (bNoPlay) then
return self.tweenAnimations[index]
else
for k, v in pairs(self.tweenAnimations or {}) do
if (k == index and v.bShouldPlay) then
return v
end
end
end
end
function meta:IsPlayingTweenAnimation(index)
for k, v in pairs(self.tweenAnimations or {}) do
if (v.bShouldPlay and index == k) then
return true
end
end
return false
end
function meta:StopAnimations(bRemove)
for k, v in pairs(self.tweenAnimations or {}) do
if (v.bShouldPlay) then
v:ForceComplete()
if (bRemove) then
self.tweenAnimations[k] = nil
end
end
end
end
function meta:CreateAnimation(length, data)
local animations = self.tweenAnimations or {}
self.tweenAnimations = animations
if (self.SetAnimationEnabled) then
self:SetAnimationEnabled(true)
end
local index = data.index or 1
local bCancelPrevious = data.bCancelPrevious == nil and false or data.bCancelPrevious
local bIgnoreConfig = SERVER or (data.bIgnoreConfig == nil and false or data.bIgnoreConfig)
if (bCancelPrevious and self:IsPlayingTweenAnimation()) then
for _, v in pairs(animations) do
v:set(v.duration)
end
end
local animation = ix.tween.new(
((length == 0 and 1 or length) or 1) * (bIgnoreConfig and 1 or ix.option.Get("animationScale", 1)),
data.subject or self,
data.target or {},
data.easing or "linear"
)
animation.index = index
animation.bIgnoreConfig = bIgnoreConfig
animation.bAutoFire = (data.bAutoFire == nil and true or data.bAutoFire)
animation.bRemoveOnComplete = (data.bRemoveOnComplete == nil and true or data.bRemoveOnComplete)
animation.Think = data.Think
animation.OnComplete = data.OnComplete
animation.ForceComplete = function(anim)
anim:set(anim.duration)
end
-- @todo don't use ridiculous method chaining
animation.CreateAnimation = function(currentAnimation, newLength, newData)
newData.bAutoFire = false
newData.index = currentAnimation.index + 1
local oldOnComplete = currentAnimation.OnComplete
local newAnimation = currentAnimation.subject:CreateAnimation(newLength, newData)
currentAnimation.OnComplete = function(...)
if (oldOnComplete) then
oldOnComplete(...)
end
newAnimation:Fire()
end
return newAnimation
end
if (length == 0 or (!animation.bIgnoreConfig and ix.option.Get("disableAnimations", false))) then
animation.Fire = function(anim)
anim:set(anim.duration)
anim.bShouldPlay = true
end
else
animation.Fire = function(anim)
anim:set(0)
anim.bShouldPlay = true
end
end
-- we can assume if we're using this library, we're not going to use the built-in
-- AnimationTo functions, so override AnimationThink with our own
self.AnimationThink = TweenAnimationThink
-- fire right away if autofire is enabled
if (animation.bAutoFire) then
animation:Fire()
end
self.tweenAnimations[index] = animation
return animation
end
end
if (CLIENT) then
local panelMeta = FindMetaTable("Panel")
ix.util.InstallAnimationMethods(panelMeta)
end
================================================
FILE: gamemode/core/libs/sh_anims.lua
================================================
--[[--
Player model animation.
Helix comes with support for using NPC animations/models as regular player models by manually translating animations. There are
a few standard animation sets that are built-in that should cover most non-player models:
citizen_male
citizen_female
metrocop
overwatch
vortigaunt
player
zombie
fastZombie
If you find that your models are T-posing when they work elsewhere, you'll probably need to set the model class for your
model with `ix.anim.SetModelClass` in order for the correct animations to be used. If you'd like to add your own animation
class, simply add to the `ix.anim` table with a model class name and the required animation translation table.
]]
-- @module ix.anim
ix.anim = ix.anim or {}
ix.anim.citizen_male = {
normal = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
pistol = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_RANGE_ATTACK_PISTOL},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_ATTACK_PISTOL_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
reload = ACT_RELOAD_PISTOL
},
smg = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
reload = ACT_GESTURE_RELOAD_SMG1
},
shotgun = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
},
grenade = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_RANGE_ATTACK_THROW
},
melee = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_MELEE_ATTACK_SWING
},
glide = ACT_GLIDE,
vehicle = {
["prop_vehicle_prisoner_pod"] = {"podpose", Vector(-3, 0, 0)},
["prop_vehicle_jeep"] = {ACT_BUSY_SIT_CHAIR, Vector(14, 0, -14)},
["prop_vehicle_airboat"] = {ACT_BUSY_SIT_CHAIR, Vector(8, 0, -20)},
chair = {ACT_BUSY_SIT_CHAIR, Vector(1, 0, -23)}
},
}
ix.anim.citizen_female = {
normal = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
pistol = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
reload = ACT_RELOAD_PISTOL
},
smg = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
reload = ACT_GESTURE_RELOAD_SMG1
},
shotgun = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
},
grenade = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_RANGE_ATTACK_THROW
},
melee = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_MELEE_ATTACK_SWING
},
glide = ACT_GLIDE,
vehicle = ix.anim.citizen_male.vehicle
}
ix.anim.metrocop = {
normal = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
pistol = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
[ACT_MP_WALK] = {ACT_WALK_PISTOL, ACT_WALK_AIM_PISTOL},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN_PISTOL, ACT_RUN_AIM_PISTOL},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
reload = ACT_GESTURE_RELOAD_PISTOL
},
smg = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
shotgun = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW},
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
grenade = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_COMBINE_THROW_GRENADE
},
melee = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_MELEE_ATTACK_SWING_GESTURE
},
glide = ACT_GLIDE,
vehicle = {
chair = {ACT_COVER_PISTOL_LOW, Vector(5, 0, -5)},
["prop_vehicle_airboat"] = {ACT_COVER_PISTOL_LOW, Vector(10, 0, 0)},
["prop_vehicle_jeep"] = {ACT_COVER_PISTOL_LOW, Vector(18, -2, 4)},
["prop_vehicle_prisoner_pod"] = {ACT_IDLE, Vector(-4, -0.5, 0)}
}
}
ix.anim.overwatch = {
normal = {
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
pistol = {
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
smg = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
shotgun = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SHOTGUN},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_SHOTGUN},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_SHOTGUN},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
grenade = {
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
melee = {
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
[ACT_LAND] = {ACT_RESET, ACT_RESET},
attack = ACT_MELEE_ATTACK_SWING_GESTURE
},
glide = ACT_GLIDE
}
ix.anim.vortigaunt = {
melee = {
["attack"] = ACT_MELEE_ATTACK1,
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"},
[ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
},
grenade = {
["attack"] = ACT_MELEE_ATTACK1,
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"},
[ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
[ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK}
},
normal = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
["attack"] = ACT_MELEE_ATTACK1
},
pistol = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"},
[ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"},
["reload"] = ACT_IDLE,
[ACT_MP_RUN] = {ACT_RUN, "run_all_TC"},
[ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK},
[ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"}
},
shotgun = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"},
[ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"},
["reload"] = ACT_IDLE,
[ACT_MP_RUN] = {ACT_RUN, "run_all_TC"},
[ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK},
[ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"}
},
smg = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"},
[ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"},
["reload"] = ACT_IDLE,
[ACT_MP_RUN] = {ACT_RUN, "run_all_TC"},
[ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK},
[ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"}
},
beam = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
["attack"] = ACT_GESTURE_RANGE_ATTACK1,
["reload"] = ACT_IDLE,
["glide"] = {ACT_RUN, ACT_RUN}
},
glide = "jump_holding_glide"
}
ix.anim.player = {
normal = {
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE,
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH,
[ACT_MP_WALK] = ACT_HL2MP_WALK,
[ACT_MP_RUN] = ACT_HL2MP_RUN,
[ACT_LAND] = {ACT_RESET, ACT_RESET}
},
passive = {
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_PASSIVE,
[ACT_MP_WALK] = ACT_HL2MP_WALK_PASSIVE,
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_PASSIVE,
[ACT_MP_RUN] = ACT_HL2MP_RUN_PASSIVE,
[ACT_LAND] = {ACT_RESET, ACT_RESET}
}
}
ix.anim.zombie = {
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_ZOMBIE,
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE,
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_01,
[ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_02,
[ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE,
[ACT_LAND] = {ACT_RESET, ACT_RESET}
}
ix.anim.fastZombie = {
[ACT_MP_STAND_IDLE] = ACT_HL2MP_WALK_ZOMBIE,
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE,
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_05,
[ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_06,
[ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE_FAST,
[ACT_LAND] = {ACT_RESET, ACT_RESET}
}
local translations = {}
--- Sets a model's animation class.
-- @realm shared
-- @string model Model name to set the animation class for
-- @string class Animation class to assign to the model
-- @usage ix.anim.SetModelClass("models/police.mdl", "metrocop")
function ix.anim.SetModelClass(model, class)
if (!ix.anim[class]) then
error("'" .. tostring(class) .. "' is not a valid animation class!")
end
translations[model:lower()] = class
end
--- Gets a model's animation class.
-- @realm shared
-- @string model Model to get the animation class for
-- @treturn[1] string Animation class of the model
-- @treturn[2] nil If there was no animation associated with the given model
-- @usage ix.anim.GetModelClass("models/police.mdl")
-- > metrocop
function ix.anim.GetModelClass(model)
model = string.lower(model)
local class = translations[model]
if (!class and string.find(model, "/player")) then
return "player"
end
class = class or "citizen_male"
if (class == "citizen_male" and (
string.find(model, "female") or
string.find(model, "alyx") or
string.find(model, "mossman"))) then
class = "citizen_female"
end
return class
end
ix.anim.SetModelClass("models/police.mdl", "metrocop")
ix.anim.SetModelClass("models/combine_super_soldier.mdl", "overwatch")
ix.anim.SetModelClass("models/combine_soldier_prisonGuard.mdl", "overwatch")
ix.anim.SetModelClass("models/combine_soldier.mdl", "overwatch")
ix.anim.SetModelClass("models/vortigaunt.mdl", "vortigaunt")
ix.anim.SetModelClass("models/vortigaunt_blue.mdl", "vortigaunt")
ix.anim.SetModelClass("models/vortigaunt_doctor.mdl", "vortigaunt")
ix.anim.SetModelClass("models/vortigaunt_slave.mdl", "vortigaunt")
if (SERVER) then
util.AddNetworkString("ixSequenceSet")
util.AddNetworkString("ixSequenceReset")
local playerMeta = FindMetaTable("Player")
--- Player anim methods
-- @classmod Player
--- Forces this player's model to play an animation sequence. It also prevents the player from firing their weapon while the
-- animation is playing.
-- @realm server
-- @string sequence Name of the animation sequence to play
-- @func[opt=nil] callback Function to call when the animation finishes. This is also called immediately if the animation
-- fails to play
-- @number[opt=nil] time How long to play the animation for. This defaults to the duration of the animation
-- @bool[opt=false] bNoFreeze Whether or not to avoid freezing this player in place while the animation is playing
-- @see LeaveSequence
function playerMeta:ForceSequence(sequence, callback, time, bNoFreeze)
hook.Run("PlayerEnterSequence", self, sequence, callback, time, bNoFreeze)
if (!sequence) then
net.Start("ixSequenceReset")
net.WriteEntity(self)
net.Broadcast()
return
end
sequence = self:LookupSequence(tostring(sequence))
if (sequence and sequence > 0) then
time = time or self:SequenceDuration(sequence)
self.ixCouldShoot = self:GetNetVar("canShoot", false)
self.ixSeqCallback = callback
self:SetCycle(0)
self:SetPlaybackRate(1)
self:SetNetVar("forcedSequence", sequence)
self:SetNetVar("canShoot", false)
if (!bNoFreeze) then
self:SetMoveType(MOVETYPE_NONE)
end
if (time > 0) then
timer.Create("ixSeq"..self:EntIndex(), time, 1, function()
if (IsValid(self)) then
self:LeaveSequence()
end
end)
end
net.Start("ixSequenceSet")
net.WriteEntity(self)
net.Broadcast()
return time
elseif (callback) then
callback()
end
return false
end
--- Forcefully stops this player's model from playing an animation that was started by `ForceSequence`.
-- @realm server
function playerMeta:LeaveSequence()
hook.Run("PlayerLeaveSequence", self)
net.Start("ixSequenceReset")
net.WriteEntity(self)
net.Broadcast()
self:SetNetVar("canShoot", self.ixCouldShoot)
self:SetNetVar("forcedSequence", nil)
self:SetMoveType(MOVETYPE_WALK)
self.ixCouldShoot = nil
if (self.ixSeqCallback) then
self:ixSeqCallback()
end
end
else
net.Receive("ixSequenceSet", function()
local entity = net.ReadEntity()
if (IsValid(entity)) then
hook.Run("PlayerEnterSequence", entity)
end
end)
net.Receive("ixSequenceReset", function()
local entity = net.ReadEntity()
if (IsValid(entity)) then
hook.Run("PlayerLeaveSequence", entity)
end
end)
end
================================================
FILE: gamemode/core/libs/sh_attribs.lua
================================================
-- @module ix.attributes
if (!ix.char) then
include("sh_character.lua")
end
ix.attributes = ix.attributes or {}
ix.attributes.list = ix.attributes.list or {}
function ix.attributes.LoadFromDir(directory)
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
local niceName = v:sub(4, -5)
ATTRIBUTE = ix.attributes.list[niceName] or {}
if (PLUGIN) then
ATTRIBUTE.plugin = PLUGIN.uniqueID
end
ix.util.Include(directory.."/"..v)
ATTRIBUTE.name = ATTRIBUTE.name or "Unknown"
ATTRIBUTE.description = ATTRIBUTE.description or "No description availalble."
ix.attributes.list[niceName] = ATTRIBUTE
ATTRIBUTE = nil
end
end
function ix.attributes.Setup(client)
local character = client:GetCharacter()
if (character) then
for k, v in pairs(ix.attributes.list) do
if (v.OnSetup) then
v:OnSetup(client, character:GetAttribute(k, 0))
end
end
end
end
do
--- Character attribute methods
-- @classmod Character
local charMeta = ix.meta.character
if (SERVER) then
util.AddNetworkString("ixAttributeUpdate")
--- Increments one of this character's attributes by the given amount.
-- @realm server
-- @string key Name of the attribute to update
-- @number value Amount to add to the attribute
function charMeta:UpdateAttrib(key, value)
local attribute = ix.attributes.list[key]
local client = self:GetPlayer()
if (attribute) then
local attrib = self:GetAttributes()
attrib[key] = math.min((attrib[key] or 0) + value, attribute.maxValue or ix.config.Get("maxAttributes", 100))
if (IsValid(client)) then
net.Start("ixAttributeUpdate")
net.WriteUInt(self:GetID(), 32)
net.WriteString(key)
net.WriteFloat(attrib[key])
net.Send(client)
if (attribute.Setup) then
attribute.Setup(attrib[key])
end
end
self:SetAttributes(attrib)
end
hook.Run("CharacterAttributeUpdated", client, self, key, value)
end
--- Sets the value of an attribute for this character.
-- @realm server
-- @string key Name of the attribute to update
-- @number value New value for the attribute
function charMeta:SetAttrib(key, value)
local attribute = ix.attributes.list[key]
local client = self:GetPlayer()
if (attribute) then
local attrib = self:GetAttributes()
attrib[key] = value
if (IsValid(client)) then
net.Start("ixAttributeUpdate")
net.WriteUInt(self:GetID(), 32)
net.WriteString(key)
net.WriteFloat(attrib[key])
net.Send(client)
if (attribute.Setup) then
attribute.Setup(attrib[key])
end
end
self:SetAttributes(attrib)
end
hook.Run("CharacterAttributeUpdated", client, self, key, value)
end
--- Temporarily increments one of this character's attributes. Useful for things like consumable items.
-- @realm server
-- @string boostID Unique ID to use for the boost to remove it later
-- @string attribID Name of the attribute to boost
-- @number boostAmount Amount to increase the attribute by
function charMeta:AddBoost(boostID, attribID, boostAmount)
local boosts = self:GetVar("boosts", {})
boosts[attribID] = boosts[attribID] or {}
boosts[attribID][boostID] = boostAmount
hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, boostAmount)
return self:SetVar("boosts", boosts, nil, self:GetPlayer())
end
--- Removes a temporary boost from this character.
-- @realm server
-- @string boostID Unique ID of the boost to remove
-- @string attribID Name of the attribute that was boosted
function charMeta:RemoveBoost(boostID, attribID)
local boosts = self:GetVar("boosts", {})
boosts[attribID] = boosts[attribID] or {}
boosts[attribID][boostID] = nil
hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, true)
return self:SetVar("boosts", boosts, nil, self:GetPlayer())
end
else
net.Receive("ixAttributeUpdate", function()
local id = net.ReadUInt(32)
local character = ix.char.loaded[id]
if (character) then
local key = net.ReadString()
local value = net.ReadFloat()
character:GetAttributes()[key] = value
end
end)
end
--- Returns all boosts that this character has for the given attribute. This is only valid on the server and owning client.
-- @realm shared
-- @string attribID Name of the attribute to find boosts for
-- @treturn[1] table Table of boosts that this character has for the attribute
-- @treturn[2] nil If the character has no boosts for the given attribute
function charMeta:GetBoost(attribID)
local boosts = self:GetBoosts()
return boosts[attribID]
end
--- Returns all boosts that this character has. This is only valid on the server and owning client.
-- @realm shared
-- @treturn table Table of boosts this character has
function charMeta:GetBoosts()
return self:GetVar("boosts", {})
end
--- Returns the current value of an attribute. This is only valid on the server and owning client.
-- @realm shared
-- @string key Name of the attribute to get
-- @number default Value to return if the attribute doesn't exist
-- @treturn number Value of the attribute
function charMeta:GetAttribute(key, default)
local att = self:GetAttributes()[key] or default
local boosts = self:GetBoosts()[key]
if (boosts) then
for _, v in pairs(boosts) do
att = att + v
end
end
return att
end
end
================================================
FILE: gamemode/core/libs/sh_business.lua
================================================
if (SERVER) then
util.AddNetworkString("ixBusinessBuy")
util.AddNetworkString("ixBusinessResponse")
util.AddNetworkString("ixShipmentUse")
util.AddNetworkString("ixShipmentOpen")
util.AddNetworkString("ixShipmentClose")
net.Receive("ixBusinessBuy", function(length, client)
if (client.ixNextBusiness and client.ixNextBusiness > CurTime()) then
client:NotifyLocalized("businessTooFast")
return
end
local char = client:GetCharacter()
if (!char) then
return
end
local indicies = net.ReadUInt(8)
local items = {}
for _ = 1, indicies do
items[net.ReadString()] = net.ReadUInt(8)
end
if (table.IsEmpty(items)) then
return
end
local cost = 0
for k, v in pairs(items) do
local itemTable = ix.item.list[k]
if (itemTable and hook.Run("CanPlayerUseBusiness", client, k) != false) then
local amount = math.Clamp(tonumber(v) or 0, 0, 10)
items[k] = amount
if (amount == 0) then
items[k] = nil
else
cost = cost + (amount * (itemTable.price or 0))
end
else
items[k] = nil
end
end
if (table.IsEmpty(items)) then
return
end
if (char:HasMoney(cost)) then
char:TakeMoney(cost)
local entity = ents.Create("ix_shipment")
entity:Spawn()
entity:SetPos(client:GetItemDropPos(entity))
entity:SetItems(items)
entity:SetNetVar("owner", char:GetID())
local shipments = char:GetVar("charEnts") or {}
table.insert(shipments, entity)
char:SetVar("charEnts", shipments, true)
net.Start("ixBusinessResponse")
net.Send(client)
hook.Run("CreateShipment", client, entity)
client.ixNextBusiness = CurTime() + 0.5
end
end)
net.Receive("ixShipmentUse", function(length, client)
local uniqueID = net.ReadString()
local drop = net.ReadBool()
local entity = client.ixShipment
local itemTable = ix.item.list[uniqueID]
if (itemTable and IsValid(entity)) then
if (entity:GetPos():Distance(client:GetPos()) > 128) then
client.ixShipment = nil
return
end
local amount = entity.items[uniqueID]
if (amount and amount > 0) then
if (entity.items[uniqueID] <= 0) then
entity.items[uniqueID] = nil
end
if (drop) then
ix.item.Spawn(uniqueID, entity:GetPos() + Vector(0, 0, 16), function(item, itemEntity)
if (IsValid(client)) then
itemEntity.ixSteamID = client:SteamID()
itemEntity.ixCharID = client:GetCharacter():GetID()
end
end)
else
local status, _ = client:GetCharacter():GetInventory():Add(uniqueID)
if (!status) then
return client:NotifyLocalized("noFit")
end
end
hook.Run("ShipmentItemTaken", client, uniqueID, amount)
entity.items[uniqueID] = entity.items[uniqueID] - 1
if (entity:GetItemCount() < 1) then
entity:GibBreakServer(Vector(0, 0, 0.5))
entity:Remove()
end
end
end
end)
net.Receive("ixShipmentClose", function(length, client)
local entity = client.ixShipment
if (IsValid(entity)) then
entity.ixInteractionDirty = false
client.ixShipment = nil
end
end)
else
net.Receive("ixShipmentOpen", function()
local entity = net.ReadEntity()
local items = net.ReadTable()
ix.gui.shipment = vgui.Create("ixShipment")
ix.gui.shipment:SetItems(entity, items)
end)
end
================================================
FILE: gamemode/core/libs/sh_character.lua
================================================
--[[--
Character creation and management.
**NOTE:** For the most part you shouldn't use this library unless you know what you're doing. You can very easily corrupt
character data using these functions!
]]
-- @module ix.char
ix.char = ix.char or {}
--- Characters that are currently loaded into memory. This is **not** a table of characters that players are currently using.
-- Characters are automatically loaded when a player joins the server. Entries are not cleared once the player disconnects, as
-- some data is needed after the player has disconnected. Clients will also keep their own version of this table, so don't
-- expect it to be the same as the server's.
--
-- The keys in this table are the IDs of characters, and the values are the `Character` objects that the ID corresponds to.
-- @realm shared
-- @table ix.char.loaded
-- @usage print(ix.char.loaded[1])
-- > character[1]
ix.char.loaded = ix.char.loaded or {}
--- Variables that are stored on characters. This table is populated automatically by `ix.char.RegisterVar`.
-- @realm shared
-- @table ix.char.vars
-- @usage print(ix.char.vars["name"])
-- > table: 0xdeadbeef
ix.char.vars = ix.char.vars or {}
--- Functions similar to `ix.char.loaded`, but is serverside only. This contains a table of all loaded characters grouped by
-- the SteamID64 of the player that owns them.
-- @realm server
-- @table ix.char.cache
ix.char.cache = ix.char.cache or {}
ix.util.Include("helix/gamemode/core/meta/sh_character.lua")
if (SERVER) then
--- Creates a character object with its assigned properties and saves it to the database.
-- @realm server
-- @tab data Properties to assign to this character. If fields are missing from the table, then it will use the default
-- value for that property
-- @func callback Function to call after the character saves
function ix.char.Create(data, callback)
local timeStamp = math.floor(os.time())
data.money = data.money or ix.config.Get("defaultMoney", 0)
data.schema = Schema and Schema.folder or "helix"
data.createTime = timeStamp
data.lastJoinTime = timeStamp
local query = mysql:Insert("ix_characters")
query:Insert("name", data.name or "")
query:Insert("description", data.description or "")
query:Insert("model", data.model or "models/error.mdl")
query:Insert("schema", Schema and Schema.folder or "helix")
query:Insert("create_time", data.createTime)
query:Insert("last_join_time", data.lastJoinTime)
query:Insert("steamid", data.steamID)
query:Insert("faction", data.faction or "Unknown")
query:Insert("money", data.money)
query:Insert("data", util.TableToJSON(data.data or {}))
query:Callback(function(result, status, lastID)
local invQuery = mysql:Insert("ix_inventories")
invQuery:Insert("character_id", lastID)
invQuery:Callback(function(invResult, invStats, invLastID)
local client = player.GetBySteamID64(data.steamID)
ix.char.RestoreVars(data, data)
local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight")
local character = ix.char.New(data, lastID, client, data.steamID)
local inventory = ix.inventory.Create(w, h, invLastID)
character.vars.inv = {inventory}
inventory:SetOwner(lastID)
ix.char.loaded[lastID] = character
table.insert(ix.char.cache[data.steamID], lastID)
if (callback) then
callback(lastID)
end
end)
invQuery:Execute()
end)
query:Execute()
end
--- Loads all of a player's characters into memory.
-- @realm server
-- @player client Player to load the characters for
-- @func[opt=nil] callback Function to call when the characters have been loaded
-- @bool[opt=false] bNoCache Whether or not to skip the cache; players that leave and join again later will already have
-- their characters loaded which will skip the database query and load quicker
-- @number[opt=nil] id The ID of a specific character to load instead of all of the player's characters
function ix.char.Restore(client, callback, bNoCache, id)
local steamID64 = client:SteamID64()
local cache = ix.char.cache[steamID64]
if (cache and !bNoCache) then
for _, v in ipairs(cache) do
local character = ix.char.loaded[v]
if (character and !IsValid(character.client)) then
character.player = client
end
end
if (callback) then
callback(cache)
end
return
end
local query = mysql:Select("ix_characters")
query:Select("id")
ix.char.RestoreVars(query)
query:Where("schema", Schema.folder)
query:Where("steamid", steamID64)
if (id) then
query:Where("id", id)
end
query:Callback(function(result)
local characters = {}
for _, v in ipairs(result or {}) do
local charID = tonumber(v.id)
if (charID) then
local data = {
steamID = steamID64
}
ix.char.RestoreVars(data, v)
characters[#characters + 1] = charID
local character = ix.char.New(data, charID, client)
hook.Run("CharacterRestored", character)
character.vars.inv = {
[1] = -1,
}
local invQuery = mysql:Select("ix_inventories")
invQuery:Select("inventory_id")
invQuery:Select("inventory_type")
invQuery:Where("character_id", charID)
invQuery:Callback(function(info)
if (istable(info) and #info > 0) then
local inventories = {}
for _, v2 in pairs(info) do
if (v2.inventory_type and isstring(v2.inventory_type) and v2.inventory_type == "NULL") then
v2.inventory_type = nil
end
if (hook.Run("ShouldRestoreInventory", charID, v2.inventory_id, v2.inventory_type) != false) then
local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight")
local invType
if (v2.inventory_type) then
invType = ix.item.inventoryTypes[v2.inventory_type]
if (invType) then
w, h = invType.w, invType.h
end
end
inventories[tonumber(v2.inventory_id)] = {w, h, v2.inventory_type}
end
end
ix.inventory.Restore(inventories, nil, nil, function(inventory)
local inventoryType = inventories[inventory:GetID()][3]
if (inventoryType) then
inventory.vars.isBag = inventoryType
table.insert(character.vars.inv, inventory)
else
character.vars.inv[1] = inventory
end
inventory:SetOwner(charID)
end, true)
else
local insertQuery = mysql:Insert("ix_inventories")
insertQuery:Insert("character_id", charID)
insertQuery:Callback(function(_, status, lastID)
local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight")
local inventory = ix.inventory.Create(w, h, lastID)
inventory:SetOwner(charID)
character.vars.inv = {
inventory
}
end)
insertQuery:Execute()
end
end)
invQuery:Execute()
ix.char.loaded[charID] = character
else
ErrorNoHalt("[Helix] Attempt to load character with invalid ID '" .. tostring(id) .. "'!")
end
end
if (callback) then
callback(characters)
end
ix.char.cache[steamID64] = characters
end)
query:Execute()
end
--- Adds character properties to a table. This is done automatically by `ix.char.Restore`, so that should be used instead if
-- you are loading characters.
-- @realm server
-- @internal
-- @tab data Table of fields to apply to the table. If this is an SQL query object, it will instead populate the query with
-- `SELECT` statements for each applicable character var in `ix.char.vars`.
-- @tab characterInfo Table to apply the properties to. This can be left as `nil` if an SQL query object is passed in `data`
function ix.char.RestoreVars(data, characterInfo)
if (data.queryType) then
-- populate query
for _, v in pairs(ix.char.vars) do
if (v.field and v.fieldType and !v.bSaveLoadInitialOnly) then
data:Select(v.field)
-- if FilterValues is used, any rows that contain a value in the column that isn't in the valid values table
-- will be ignored entirely (i.e the character will not load if it has an invalid value)
if (v.FilterValues) then
data:WhereIn(v.field, v:FilterValues())
end
end
end
else
-- populate character data
for k, v in pairs(ix.char.vars) do
if (v.field and characterInfo[v.field] and !v.bSaveLoadInitialOnly) then
local value = characterInfo[v.field]
if (isnumber(v.default)) then
value = tonumber(value) or v.default
elseif (isstring(v.default)) then
value = tostring(value) == "NULL" and v.default or tostring(value or v.default)
elseif (isbool(v.default)) then
if (tostring(value) != "NULL") then
value = tobool(value)
else
value = v.default
end
elseif (istable(v.default)) then
value = istable(value) and value or util.JSONToTable(value)
end
data[k] = value
end
end
end
end
end
--- Creates a new empty `Character` object. If you are looking to create a usable character, see `ix.char.Create`.
-- @realm shared
-- @internal
-- @tab data Character vars to assign
-- @number id Unique ID of the character
-- @player client Player that will own the character
-- @string[opt=client:SteamID64()] steamID SteamID64 of the player that will own the character
function ix.char.New(data, id, client, steamID)
if (data.name) then
data.name = data.name:gsub("#", "#")
end
if (data.description) then
data.description = data.description:gsub("#", "#")
end
local character = setmetatable({vars = {}}, ix.meta.character)
for k, v in pairs(data) do
if (v != nil) then
character.vars[k] = v
end
end
character.id = id or 0
character.player = client
if (SERVER and IsValid(client) or steamID) then
character.steamID = IsValid(client) and client:SteamID64() or steamID
end
return character
end
ix.char.varHooks = ix.char.varHooks or {}
function ix.char.HookVar(varName, hookName, func)
ix.char.varHooks[varName] = ix.char.varHooks[varName] or {}
ix.char.varHooks[varName][hookName] = func
end
do
--- Default character vars
-- @classmod Character
--- Sets this character's name. This is automatically networked.
-- @realm server
-- @string name New name for the character
-- @function SetName
--- Returns this character's name
-- @realm shared
-- @treturn string This character's current name
-- @function GetName
ix.char.RegisterVar("name", {
field = "name",
fieldType = ix.type.string,
default = "John Doe",
index = 1,
OnValidate = function(self, value, payload, client)
if (!value) then
return false, "invalid", "name"
end
value = tostring(value):gsub("\r\n", ""):gsub("\n", "")
value = string.Trim(value)
local minLength = ix.config.Get("minNameLength", 4)
local maxLength = ix.config.Get("maxNameLength", 32)
if (value:utf8len() < minLength) then
return false, "nameMinLen", minLength
elseif (!value:find("%S")) then
return false, "invalid", "name"
elseif (value:gsub("%s", ""):utf8len() > maxLength) then
return false, "nameMaxLen", maxLength
end
return hook.Run("GetDefaultCharacterName", client, payload.faction) or value:utf8sub(1, 70)
end,
OnPostSetup = function(self, panel, payload)
local faction = ix.faction.indices[payload.faction]
local name, disabled = hook.Run("GetDefaultCharacterName", LocalPlayer(), payload.faction)
if (name) then
panel:SetText(name)
payload:Set("name", name)
end
if (disabled) then
panel:SetDisabled(true)
panel:SetEditable(false)
end
panel:SetBackgroundColor(faction.color or Color(255, 255, 255, 25))
end
})
--- Sets this character's physical description. This is automatically networked.
-- @realm server
-- @string description New description for this character
-- @function SetDescription
--- Returns this character's physical description.
-- @realm shared
-- @treturn string This character's current description
-- @function GetDescription
ix.char.RegisterVar("description", {
field = "description",
fieldType = ix.type.text,
default = "",
index = 2,
OnValidate = function(self, value, payload)
value = string.Trim((tostring(value):gsub("\r\n", ""):gsub("\n", "")))
local minLength = ix.config.Get("minDescriptionLength", 16)
if (value:utf8len() < minLength) then
return false, "descMinLen", minLength
elseif (!value:find("%s+") or !value:find("%S")) then
return false, "invalid", "description"
end
return value
end,
OnPostSetup = function(self, panel, payload)
panel:SetMultiline(true)
panel:SetFont("ixMenuButtonFont")
panel:SetTall(panel:GetTall() * 2 + 6) -- add another line
panel.AllowInput = function(_, character)
if (character == "\n" or character == "\r") then
return true
end
end
end,
alias = "Desc"
})
--- Sets this character's model. This sets the player's current model to the given one, and saves it to the character.
-- It is automatically networked.
-- @realm server
-- @string model New model for the character
-- @function SetModel
--- Returns this character's model.
-- @realm shared
-- @treturn string This character's current model
-- @function GetModel
ix.char.RegisterVar("model", {
field = "model",
fieldType = ix.type.string,
default = "models/error.mdl",
index = 3,
OnSet = function(character, value)
local client = character:GetPlayer()
if (IsValid(client) and client:GetCharacter() == character) then
client:SetModel(value)
end
character.vars.model = value
end,
OnGet = function(character, default)
return character.vars.model or default
end,
OnDisplay = function(self, container, payload)
local scroll = container:Add("DScrollPanel")
scroll:Dock(FILL) -- TODO: don't fill so we can allow other panels
scroll.Paint = function(panel, width, height)
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, Color(255, 255, 255, 25))
end
local layout = scroll:Add("DIconLayout")
layout:Dock(FILL)
layout:SetSpaceX(1)
layout:SetSpaceY(1)
local faction = ix.faction.indices[payload.faction]
if (faction) then
local models = faction:GetModels(LocalPlayer())
for k, v in SortedPairs(models) do
local icon = layout:Add("SpawnIcon")
icon:SetSize(64, 128)
icon:InvalidateLayout(true)
icon.DoClick = function(this)
payload:Set("model", k)
end
icon.PaintOver = function(this, w, h)
if (payload.model == k) then
local color = ix.config.Get("color", color_white)
surface.SetDrawColor(color.r, color.g, color.b, 200)
for i = 1, 3 do
local i2 = i * 2
surface.DrawOutlinedRect(i, i, w - i2, h - i2)
end
end
end
if (isstring(v)) then
icon:SetModel(v)
else
icon:SetModel(v[1], v[2] or 0, v[3])
end
end
end
return scroll
end,
OnValidate = function(self, value, payload, client)
local faction = ix.faction.indices[payload.faction]
if (faction) then
local models = faction:GetModels(client)
if (!payload.model or !models[payload.model]) then
return false, "needModel"
end
else
return false, "needModel"
end
end,
OnAdjust = function(self, client, data, value, newData)
local faction = ix.faction.indices[data.faction]
if (faction) then
local model = faction:GetModels(client)[value]
if (isstring(model)) then
newData.model = model
elseif (istable(model)) then
newData.model = model[1]
-- save skin/bodygroups to character data
local bodygroups = {}
for i = 1, #model[3] do
bodygroups[i - 1] = tonumber(model[3][i]) or 0
end
newData.data = newData.data or {}
newData.data.skin = model[2] or 0
newData.data.groups = bodygroups
end
end
end,
ShouldDisplay = function(self, container, payload)
local faction = ix.faction.indices[payload.faction]
return #faction:GetModels(LocalPlayer()) > 1
end
})
-- SetClass shouldn't be used here, character:JoinClass should be used instead
--- Returns this character's current class.
-- @realm shared
-- @treturn number Index of the class this character is in
-- @function GetClass
ix.char.RegisterVar("class", {
bNoDisplay = true,
})
--- Sets this character's faction. Note that this doesn't do the initial setup for the player after the faction has been
-- changed, so you'll have to update some character vars manually.
-- @realm server
-- @number faction Index of the faction to transfer this character to
-- @function SetFaction
--- Returns this character's faction.
-- @realm shared
-- @treturn number Index of the faction this character is currently in
-- @function GetFaction
ix.char.RegisterVar("faction", {
field = "faction",
fieldType = ix.type.string,
default = "Citizen",
bNoDisplay = true,
FilterValues = function(self)
-- make sequential table of faction unique IDs
local values = {}
for k, v in ipairs(ix.faction.indices) do
values[k] = v.uniqueID
end
return values
end,
OnSet = function(self, value)
local client = self:GetPlayer()
if (IsValid(client)) then
local oldVar = self:GetFaction()
self.vars.faction = ix.faction.indices[value] and ix.faction.indices[value].uniqueID
client:SetTeam(value)
-- @todo refactor networking of character vars so this doesn't need to be repeated on every OnSet override
net.Start("ixCharacterVarChanged")
net.WriteUInt(self:GetID(), 32)
net.WriteString("faction")
net.WriteType(self.vars.faction)
net.Broadcast()
hook.Run("CharacterVarChanged", self, "faction", oldVar, value)
end
end,
OnGet = function(self, default)
local faction = ix.faction.teams[self.vars.faction]
return faction and faction.index or 0
end,
OnValidate = function(self, index, data, client)
if (index and client:HasWhitelist(index)) then
return true
end
return false
end,
OnAdjust = function(self, client, data, value, newData)
newData.faction = ix.faction.indices[value].uniqueID
end
})
-- attribute manipulation should be done with methods from the ix.attributes library
ix.char.RegisterVar("attributes", {
field = "attributes",
fieldType = ix.type.text,
default = {},
index = 4,
category = "attributes",
isLocal = true,
OnDisplay = function(self, container, payload)
local maximum = hook.Run("GetDefaultAttributePoints", LocalPlayer(), payload) or 10
if (maximum < 1) then
return
end
local attributes = container:Add("DPanel")
attributes:Dock(TOP)
local y
local total = 0
payload.attributes = {}
-- total spendable attribute points
local totalBar = attributes:Add("ixAttributeBar")
totalBar:SetMax(maximum)
totalBar:SetValue(maximum)
totalBar:Dock(TOP)
totalBar:DockMargin(2, 2, 2, 2)
totalBar:SetText(L("attribPointsLeft"))
totalBar:SetReadOnly(true)
totalBar:SetColor(Color(20, 120, 20, 255))
y = totalBar:GetTall() + 4
for k, v in SortedPairsByMemberValue(ix.attributes.list, "name") do
payload.attributes[k] = 0
local bar = attributes:Add("ixAttributeBar")
bar:SetMax(v.maxValue or maximum)
bar:Dock(TOP)
bar:DockMargin(2, 2, 2, 2)
bar:SetText(L(v.name))
bar.OnChanged = function(this, difference)
if ((total + difference) > maximum) then
return false
end
total = total + difference
payload.attributes[k] = payload.attributes[k] + difference
totalBar:SetValue(totalBar.value - difference)
end
if (v.noStartBonus) then
bar:SetReadOnly()
end
y = y + bar:GetTall() + 4
end
attributes:SetTall(y)
return attributes
end,
OnValidate = function(self, value, data, client)
if (value != nil) then
if (istable(value)) then
local count = 0
for _, v in pairs(value) do
count = count + v
end
if (count > (hook.Run("GetDefaultAttributePoints", client, count) or 10)) then
return false, "unknownError"
end
else
return false, "unknownError"
end
end
end,
ShouldDisplay = function(self, container, payload)
return !table.IsEmpty(ix.attributes.list)
end
})
--- Sets this character's current money. Money is only networked to the player that owns this character.
-- @realm server
-- @number money New amount of money this character should have
-- @function SetMoney
--- Returns this character's money. This is only valid on the server and the owning client.
-- @realm shared
-- @treturn number Current money of this character
-- @function GetMoney
ix.char.RegisterVar("money", {
field = "money",
fieldType = ix.type.number,
default = 0,
isLocal = true,
bNoDisplay = true
})
--- Sets a data field on this character. This is useful for storing small bits of data that you need persisted on this
-- character. This is networked only to the owning client. If you are going to be accessing this data field frequently with
-- a getter/setter, consider using `ix.char.RegisterVar` instead.
-- @realm server
-- @string key Name of the field that holds the data
-- @param value Any value to store in the field, as long as it's supported by GMod's JSON parser
-- @function SetData
--- Returns a data field set on this character. If it doesn't exist, it will return the given default or `nil`. This is only
-- valid on the server and the owning client.
-- @realm shared
-- @string key Name of the field that's holding the data
-- @param default Value to return if the given key doesn't exist, or is `nil`
-- @return[1] Data stored in the field
-- @treturn[2] nil If the data doesn't exist, or is `nil`
-- @function GetData
ix.char.RegisterVar("data", {
default = {},
isLocal = true,
bNoDisplay = true,
field = "data",
fieldType = ix.type.text,
OnSet = function(character, key, value, noReplication, receiver)
local data = character:GetData()
local client = character:GetPlayer()
data[key] = value
if (!noReplication and IsValid(client)) then
net.Start("ixCharacterData")
net.WriteUInt(character:GetID(), 32)
net.WriteString(key)
net.WriteType(value)
net.Send(receiver or client)
end
character.vars.data = data
end,
OnGet = function(character, key, default)
local data = character.vars.data or {}
if (key) then
if (!data) then
return default
end
local value = data[key]
return value == nil and default or value
else
return default or data
end
end
})
ix.char.RegisterVar("var", {
default = {},
bNoDisplay = true,
OnSet = function(character, key, value, noReplication, receiver)
local data = character:GetVar()
local client = character:GetPlayer()
data[key] = value
if (!noReplication and IsValid(client)) then
local id
if (client:GetCharacter() and client:GetCharacter():GetID() == character:GetID()) then
id = client:GetCharacter():GetID()
else
id = character:GetID()
end
net.Start("ixCharacterVar")
net.WriteUInt(id, 32)
net.WriteString(key)
net.WriteType(value)
net.Send(receiver or client)
end
character.vars.vars = data
end,
OnGet = function(character, key, default)
character.vars.vars = character.vars.vars or {}
local data = character.vars.vars or {}
if (key) then
if (!data) then
return default
end
local value = data[key]
return value == nil and default or value
else
return default or data
end
end
})
--- Returns the Unix timestamp of when this character was created (i.e the value of `os.time()` at the time of creation).
-- @realm server
-- @treturn number Unix timestamp of when this character was created
-- @function GetCreateTime
ix.char.RegisterVar("createTime", {
field = "create_time",
fieldType = ix.type.number,
bNoDisplay = true,
bNoNetworking = true,
bNotModifiable = true
})
--- Returns the Unix timestamp of when this character was last used by its owning player.
-- @realm server
-- @treturn number Unix timestamp of when this character was last used
-- @function GetLastJoinTime
ix.char.RegisterVar("lastJoinTime", {
field = "last_join_time",
fieldType = ix.type.number,
bNoDisplay = true,
bNoNetworking = true,
bNotModifiable = true,
bSaveLoadInitialOnly = true
})
--- Returns the schema that this character belongs to. This is useful if you are running multiple schemas off of the same
-- database, and need to differentiate between them.
-- @realm server
-- @treturn string Schema this character belongs to
-- @function GetSchema
ix.char.RegisterVar("schema", {
field = "schema",
fieldType = ix.type.string,
bNoDisplay = true,
bNoNetworking = true,
bNotModifiable = true,
bSaveLoadInitialOnly = true
})
--- Returns the 64-bit Steam ID of the player that owns this character.
-- @realm server
-- @treturn string Owning player's Steam ID
-- @function GetSteamID
ix.char.RegisterVar("steamID", {
field = "steamid",
fieldType = ix.type.steamid,
bNoDisplay = true,
bNoNetworking = true,
bNotModifiable = true,
bSaveLoadInitialOnly = true
})
end
-- Networking information here.
do
if (SERVER) then
util.AddNetworkString("ixCharacterMenu")
util.AddNetworkString("ixCharacterChoose")
util.AddNetworkString("ixCharacterCreate")
util.AddNetworkString("ixCharacterDelete")
util.AddNetworkString("ixCharacterLoaded")
util.AddNetworkString("ixCharacterLoadFailure")
util.AddNetworkString("ixCharacterAuthed")
util.AddNetworkString("ixCharacterAuthFailed")
util.AddNetworkString("ixCharacterInfo")
util.AddNetworkString("ixCharacterData")
util.AddNetworkString("ixCharacterKick")
util.AddNetworkString("ixCharacterSet")
util.AddNetworkString("ixCharacterVar")
util.AddNetworkString("ixCharacterVarChanged")
net.Receive("ixCharacterChoose", function(length, client)
local id = net.ReadUInt(32)
if (client:GetCharacter() and client:GetCharacter():GetID() == id) then
net.Start("ixCharacterLoadFailure")
net.WriteString("@usingChar")
net.Send(client)
return
end
local character = ix.char.loaded[id]
if (character and character:GetPlayer() == client) then
local status, result = hook.Run("CanPlayerUseCharacter", client, character)
if (status == false) then
net.Start("ixCharacterLoadFailure")
net.WriteString(result or "")
net.Send(client)
return
end
local currentChar = client:GetCharacter()
if (currentChar) then
currentChar:Save()
for _, v in ipairs(currentChar:GetInventory(true)) do
if (istable(v)) then
v:RemoveReceiver(client)
end
end
end
hook.Run("PrePlayerLoadedCharacter", client, character, currentChar)
character:Setup()
client:Spawn()
hook.Run("PlayerLoadedCharacter", client, character, currentChar)
else
net.Start("ixCharacterLoadFailure")
net.WriteString("@unknownError")
net.Send(client)
ErrorNoHalt("[Helix] Attempt to load invalid character '" .. id .. "'\n")
end
end)
net.Receive("ixCharacterCreate", function(length, client)
if ((client.ixNextCharacterCreate or 0) > RealTime()) then
return
end
local maxChars = hook.Run("GetMaxPlayerCharacter", client) or ix.config.Get("maxCharacters", 5)
local charList = client.ixCharList
local charCount = table.Count(charList)
if (charCount >= maxChars) then
net.Start("ixCharacterAuthFailed")
net.WriteString("maxCharacters")
net.WriteTable({})
net.Send(client)
return
end
client.ixNextCharacterCreate = RealTime() + 1
local indicies = net.ReadUInt(8)
local payload = {}
for _ = 1, indicies do
payload[net.ReadString()] = net.ReadType()
end
local newPayload = {}
local results = {hook.Run("CanPlayerCreateCharacter", client, payload)}
if (table.remove(results, 1) == false) then
net.Start("ixCharacterAuthFailed")
net.WriteString(table.remove(results, 1) or "unknownError")
net.WriteTable(results)
net.Send(client)
return
end
for k, _ in pairs(payload) do
local info = ix.char.vars[k]
if (!info or (!info.OnValidate and info.bNoDisplay)) then
payload[k] = nil
end
end
for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do
local value = payload[k]
if (v.OnValidate) then
local result = {v:OnValidate(value, payload, client)}
if (result[1] == false) then
local fault = result[2]
table.remove(result, 2)
table.remove(result, 1)
net.Start("ixCharacterAuthFailed")
net.WriteString(fault)
net.WriteTable(result)
net.Send(client)
return
else
if (result[1] != nil) then
payload[k] = result[1]
end
if (v.OnAdjust) then
v:OnAdjust(client, payload, value, newPayload)
end
end
end
end
payload.steamID = client:SteamID64()
hook.Run("AdjustCreationPayload", client, payload, newPayload)
payload = table.Merge(payload, newPayload)
ix.char.Create(payload, function(id)
if (IsValid(client)) then
ix.char.loaded[id]:Sync(client)
net.Start("ixCharacterAuthed")
net.WriteUInt(id, 32)
net.WriteUInt(#client.ixCharList, 6)
for _, v in ipairs(client.ixCharList) do
net.WriteUInt(v, 32)
end
net.Send(client)
MsgN("Created character '" .. id .. "' for " .. client:SteamName() .. ".")
hook.Run("OnCharacterCreated", client, ix.char.loaded[id])
end
end)
end)
net.Receive("ixCharacterDelete", function(length, client)
local id = net.ReadUInt(32)
local character = ix.char.loaded[id]
local steamID = client:SteamID64()
local isCurrentChar = client:GetCharacter() and client:GetCharacter():GetID() == id
if (character and character.steamID == steamID) then
for k, v in ipairs(client.ixCharList or {}) do
if (v == id) then
table.remove(client.ixCharList, k)
end
end
hook.Run("PreCharacterDeleted", client, character)
ix.char.loaded[id] = nil
net.Start("ixCharacterDelete")
net.WriteUInt(id, 32)
net.Broadcast()
-- remove character from database
local query = mysql:Delete("ix_characters")
query:Where("id", id)
query:Where("steamid", client:SteamID64())
query:Execute()
-- DBTODO: setup relations instead
-- remove inventory from database
query = mysql:Select("ix_inventories")
query:Select("inventory_id")
query:Where("character_id", id)
query:Callback(function(result)
if (istable(result)) then
-- remove associated items from database
for _, v in ipairs(result) do
local itemQuery = mysql:Delete("ix_items")
itemQuery:Where("inventory_id", v.inventory_id)
itemQuery:Execute()
ix.item.inventories[tonumber(v.inventory_id)] = nil
end
end
local invQuery = mysql:Delete("ix_inventories")
invQuery:Where("character_id", id)
invQuery:Execute()
end)
query:Execute()
-- other plugins might need to deal with deleted characters.
hook.Run("CharacterDeleted", client, id, isCurrentChar)
if (isCurrentChar) then
client:SetNetVar("char", nil)
client:KillSilent()
client:StripAmmo()
end
end
end)
else
net.Receive("ixCharacterInfo", function()
local data = net.ReadTable()
local id = net.ReadUInt(32)
local client = net.ReadUInt(8)
ix.char.loaded[id] = ix.char.New(data, id, client)
end)
net.Receive("ixCharacterVarChanged", function()
local id = net.ReadUInt(32)
local character = ix.char.loaded[id]
if (character) then
local key = net.ReadString()
local value = net.ReadType()
character.vars[key] = value
end
end)
-- Used for setting random access vars on the "var" character var (really stupid).
-- Clean this up someday.
net.Receive("ixCharacterVar", function()
local id = net.ReadUInt(32)
local character = ix.char.loaded[id]
if (character) then
local key = net.ReadString()
local value = net.ReadType()
local oldVar = character:GetVar()[key]
character:GetVar()[key] = value
hook.Run("CharacterVarChanged", character, key, oldVar, value)
end
end)
net.Receive("ixCharacterMenu", function()
local indices = net.ReadUInt(6)
local charList = {}
for _ = 1, indices do
charList[#charList + 1] = net.ReadUInt(32)
end
if (charList) then
ix.characters = charList
end
vgui.Create("ixCharMenu")
end)
net.Receive("ixCharacterLoadFailure", function()
local message = net.ReadString()
if (isstring(message) and message:sub(1, 1) == "@") then
message = L(message:sub(2))
end
message = message != "" and message or L("unknownError")
if (IsValid(ix.gui.characterMenu)) then
ix.gui.characterMenu:OnCharacterLoadFailed(message)
else
ix.util.Notify(message)
end
end)
net.Receive("ixCharacterData", function()
local id = net.ReadUInt(32)
local key = net.ReadString()
local value = net.ReadType()
local character = ix.char.loaded[id]
if (character) then
character.vars.data = character.vars.data or {}
character:GetData()[key] = value
end
end)
net.Receive("ixCharacterDelete", function()
local id = net.ReadUInt(32)
local isCurrentChar = LocalPlayer():GetCharacter() and LocalPlayer():GetCharacter():GetID() == id
local character = ix.char.loaded[id]
ix.char.loaded[id] = nil
for k, v in ipairs(ix.characters) do
if (v == id) then
table.remove(ix.characters, k)
if (IsValid(ix.gui.characterMenu)) then
ix.gui.characterMenu:OnCharacterDeleted(character)
end
end
end
if (isCurrentChar and !IsValid(ix.gui.characterMenu)) then
vgui.Create("ixCharMenu")
end
end)
net.Receive("ixCharacterKick", function()
local isCurrentChar = net.ReadBool()
if (ix.gui.menu and ix.gui.menu:IsVisible()) then
ix.gui.menu:Remove()
end
if (!IsValid(ix.gui.characterMenu)) then
vgui.Create("ixCharMenu")
elseif (ix.gui.characterMenu:IsClosing()) then
ix.gui.characterMenu:Remove()
vgui.Create("ixCharMenu")
end
if (isCurrentChar) then
ix.gui.characterMenu.mainPanel:UpdateReturnButton(false)
end
end)
net.Receive("ixCharacterLoaded", function()
hook.Run("CharacterLoaded", ix.char.loaded[net.ReadUInt(32)])
end)
end
end
do
--- Character util functions for player
-- @classmod Player
local playerMeta = FindMetaTable("Player")
playerMeta.SteamName = playerMeta.SteamName or playerMeta.Name
--- Returns this player's currently possessed `Character` object if it exists.
-- @realm shared
-- @treturn[1] Character Currently loaded character
-- @treturn[2] nil If this player has no character loaded
function playerMeta:GetCharacter()
return ix.char.loaded[self:GetNetVar("char")]
end
playerMeta.GetChar = playerMeta.GetCharacter
--- Returns this player's current name.
-- @realm shared
-- @treturn[1] string Name of this player's currently loaded character
-- @treturn[2] string Steam name of this player if the player has no character loaded
function playerMeta:GetName()
local character = self:GetCharacter()
return character and character:GetName() or self:SteamName()
end
playerMeta.Nick = playerMeta.GetName
playerMeta.Name = playerMeta.GetName
end
================================================
FILE: gamemode/core/libs/sh_chatbox.lua
================================================
--[[--
Chat manipulation and helper functions.
Chat messages are a core part of the framework - it's takes up a good chunk of the gameplay, and is also used to interact with
the framework. Chat messages can have types or "classes" that describe how the message should be interpreted. All chat messages
will have some type of class: `ic` for regular in-character speech, `me` for actions, `ooc` for out-of-character, etc. These
chat classes can affect how the message is displayed in each player's chatbox. See `ix.chat.Register` and `ChatClassStructure`
to create your own chat classes.
]]
-- @module ix.chat
ix.chat = ix.chat or {}
--- List of all chat classes that have been registered by the framework, where each key is the name of the chat class, and value
-- is the chat class data. Accessing a chat class's data is useful for when you want to copy some functionality or properties
-- to use in your own. Note that if you're accessing this table, you should do so inside of the `InitializedChatClasses` hook.
-- @realm shared
-- @table ix.chat.classes
-- @usage print(ix.chat.classes.ic.format)
-- > "%s says \"%s\""
ix.chat.classes = ix.chat.classes or {}
if (!ix.command) then
include("sh_command.lua")
end
CAMI.RegisterPrivilege({
Name = "Helix - Bypass OOC Timer",
MinAccess = "admin"
})
-- note we can't use commas in the "color" field's default value since the metadata is separated by commas which will break the
-- formatting for that field
--- Chat messages can have different classes or "types" of messages that have different properties. This can include how the
-- text is formatted, color, hearing distance, etc.
-- @realm shared
-- @table ChatClassStructure
-- @see ix.chat.Register
-- @field[type=string] prefix What the player must type before their message in order to use this chat class. For example,
-- having a prefix of `/Y` will require to type `/Y I am yelling` in order to send a message with this chat class. This can also
-- be a table of strings if you want to allow multiple prefixes, such as `{"//", "/OOC"}`.
--
-- **NOTE:** the prefix should usually start with a `/` to be consistent with the rest of the framework. However, you are able
-- to use something different like the `LOOC` chat class where the prefixes are `.//`, `[[`, and `/LOOC`.
-- @field[type=bool,opt=false] noSpaceAfter Whether or not the `prefix` can be used without a space after it. For example, the
-- `OOC` chat class allows you to type `//my message` instead of `// my message`. **NOTE:** this only works if the last
-- character in the prefix is non-alphanumeric (i.e `noSpaceAfter` with `/Y` will not work, but `/!` will).
-- @field[type=string,opt] description Description to show to the user in the chatbox when they're using this chat class
-- @field[type=string,opt="%s: \"%s\""] format How to format a message with this chat class. The first `%s` will be the speaking
-- player's name, and the second one will be their message
-- @field[type=color,opt=Color(242 230 160)] color Color to use when displaying a message with this chat class
-- @field[type=string,opt="chatTyping"] indicator Language phrase to use when displaying the typing indicator above the
-- speaking player's head
-- @field[type=bool,opt=false] bNoIndicator Whether or not to avoid showing the typing indicator above the speaking player's
-- head
-- @field[type=string,opt=ixChatFont] font Font to use for displaying a message with this chat class
-- @field[type=bool,opt=false] deadCanChat Whether or not a dead player can send a message with this chat class
-- @field[type=number] CanHear This can be either a `number` representing how far away another player can hear this message.
-- IC messages will use the `chatRange` config, for example. This can also be a function, which returns `true` if the given
-- listener can hear the message emitted from a speaker.
-- -- message can be heard by any player 1000 units away from the speaking player
-- CanHear = 1000
-- OR
-- CanHear = function(self, speaker, listener)
-- -- the speaking player will be heard by everyone
-- return true
-- end
-- @field[type=function,opt] CanSay Function to run to check whether or not a player can send a message with this chat class.
-- By default, it will return `false` if the player is dead and `deadCanChat` is `false`. Overriding this function will prevent
-- `deadCanChat` from working, and you must implement this functionality manually.
-- CanSay = function(self, speaker, text)
-- -- the speaker will never be able to send a message with this chat class
-- return false
-- end
-- @field[type=function,opt] GetColor Function to run to set the color of a message with this chat class. You should generally
-- stick to using `color`, but this is useful for when you want the color of the message to change with some criteria.
-- GetColor = function(self, speaker, text)
-- -- each message with this chat class will be colored a random shade of red
-- return Color(math.random(120, 200), 0, 0)
-- end
-- @field[type=function,opt] OnChatAdd Function to run when a message with this chat class should be added to the chatbox. If
-- using this function, make sure you end the function by calling `chat.AddText` in order for the text to show up.
--
-- **NOTE:** using your own `OnChatAdd` function will prevent `color`, `GetColor`, or `format` from being used since you'll be
-- overriding the base function that uses those properties. In such cases you'll need to add that functionality back in
-- manually. In general, you should avoid overriding this function where possible. The `data` argument in the function is
-- whatever is passed into the same `data` argument in `ix.chat.Send`.
--
-- OnChatAdd = function(self, speaker, text, bAnonymous, data)
-- -- adds white text in the form of "Player Name: Message contents"
-- chat.AddText(color_white, speaker:GetName(), ": ", text)
-- end
--- Registers a new chat type with the information provided. Chat classes should usually be created inside of the
-- `InitializedChatClasses` hook.
-- @realm shared
-- @string chatType Name of the chat type
-- @tparam ChatClassStructure data Properties and functions to assign to this chat class
-- @usage -- this is the "me" chat class taken straight from the framework as an example
-- ix.chat.Register("me", {
-- format = "** %s %s",
-- color = Color(255, 50, 50),
-- CanHear = ix.config.Get("chatRange", 280) * 2,
-- prefix = {"/Me", "/Action"},
-- description = "@cmdMe",
-- indicator = "chatPerforming",
-- deadCanChat = true
-- })
-- @see ChatClassStructure
function ix.chat.Register(chatType, data)
chatType = string.lower(chatType)
if (!data.CanHear) then
-- Have a substitute if the canHear property is not found.
function data:CanHear(speaker, listener)
-- The speaker will be heard by everyone.
return true
end
elseif (isnumber(data.CanHear)) then
-- Use the value as a range and create a function to compare distances.
local range = data.CanHear * data.CanHear
data.range = range
function data:CanHear(speaker, listener)
-- Length2DSqr is faster than Length2D, so just check the squares.
return (speaker:GetPos() - listener:GetPos()):LengthSqr() <= self.range
end
end
-- Allow players to use this chat type by default.
if (!data.CanSay) then
function data:CanSay(speaker, text)
if (!self.deadCanChat and !speaker:Alive()) then
speaker:NotifyLocalized("noPerm")
return false
end
return true
end
end
-- Chat text color.
data.color = data.color or Color(242, 230, 160)
if (!data.OnChatAdd) then
data.format = data.format or "%s: \"%s\""
function data:OnChatAdd(speaker, text, anonymous, info)
local color = self.color
local name = anonymous and
L"someone" or hook.Run("GetCharacterName", speaker, chatType) or
(IsValid(speaker) and speaker:Name() or "Console")
if (self.GetColor) then
color = self:GetColor(speaker, text, info)
end
local translated = L2(chatType.."Format", name, text)
chat.AddText(color, translated or string.format(self.format, name, text))
end
end
if (CLIENT and data.prefix) then
if (istable(data.prefix)) then
for _, v in ipairs(data.prefix) do
if (v:utf8sub(1, 1) == "/") then
ix.command.Add(v:utf8sub(2), {
description = data.description,
arguments = ix.type.text,
indicator = data.indicator,
bNoIndicator = data.bNoIndicator,
chatClass = data,
OnCheckAccess = function() return true end,
OnRun = function(self, client, message) end
})
end
end
else
ix.command.Add(isstring(data.prefix) and data.prefix:utf8sub(2) or chatType, {
description = data.description,
arguments = ix.type.text,
indicator = data.indicator,
bNoIndicator = data.bNoIndicator,
chatClass = data,
OnCheckAccess = function() return true end,
OnRun = function(self, client, message) end
})
end
end
data.uniqueID = chatType
ix.chat.classes[chatType] = data
end
--- Identifies which chat mode should be used.
-- @realm shared
-- @player client Player who is speaking
-- @string message Message to parse
-- @bool[opt=false] bNoSend Whether or not to send the chat message after parsing
-- @treturn string Name of the chat type
-- @treturn string Message that was parsed
-- @treturn bool Whether or not the speaker should be anonymous
function ix.chat.Parse(client, message, bNoSend)
local anonymous = false
local chatType = "ic"
-- Loop through all chat classes and see if the message contains their prefix.
for k, v in pairs(ix.chat.classes) do
local isChosen = false
local chosenPrefix = ""
local noSpaceAfter = v.noSpaceAfter
-- Check through all prefixes if the chat type has more than one.
if (istable(v.prefix)) then
for _, prefix in ipairs(v.prefix) do
prefix = prefix:utf8lower()
local fullPrefix = prefix .. (noSpaceAfter and "" or " ")
-- Checking if the start of the message has the prefix.
if (message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()) then
isChosen = true
chosenPrefix = fullPrefix
break
end
end
-- Otherwise the prefix itself is checked.
elseif (isstring(v.prefix)) then
local prefix = v.prefix:utf8lower()
local fullPrefix = prefix .. (noSpaceAfter and "" or " ")
isChosen = message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()
chosenPrefix = fullPrefix
end
-- If the checks say we have the proper chat type, then the chat type is the chosen one!
-- If this is not chosen, the loop continues. If the loop doesn't find the correct chat
-- type, then it falls back to IC chat as seen by the chatType variable above.
if (isChosen) then
-- Set the chat type to the chosen one.
chatType = k
-- Remove the prefix from the chat type so it does not show in the message.
message = message:utf8sub(chosenPrefix:utf8len() + 1)
if (ix.chat.classes[k].noSpaceAfter and message:utf8sub(1, 1):match("%s")) then
message = message:utf8sub(2)
end
break
end
end
if (!message:find("%S")) then
return
end
-- Only send if needed.
if (SERVER and !bNoSend) then
-- Send the correct chat type out so other player see the message.
ix.chat.Send(client, chatType, hook.Run("PlayerMessageSend", client, chatType, message, anonymous) or message, anonymous)
end
-- Return the chosen chat type and the message that was sent if needed for some reason.
-- This would be useful if you want to send the message on your own.
return chatType, message, anonymous
end
--- Formats a string to fix basic grammar - removing extra spacing at the beginning and end, capitalizing the first character,
-- and making sure it ends in punctuation.
-- @realm shared
-- @string text String to format
-- @treturn string Formatted string
-- @usage print(ix.chat.Format("hello"))
-- > Hello.
-- @usage print(ix.chat.Format("wow!"))
-- > Wow!
function ix.chat.Format(text)
text = string.Trim(text)
local last = text:utf8sub(-1)
if (last != "." and last != "?" and last != "!" and last != "-" and last != "\"") then
text = text .. "."
end
return text:utf8sub(1, 1):utf8upper() .. text:utf8sub(2)
end
if (SERVER) then
util.AddNetworkString("ixChatMessage")
--- Send a chat message using the specified chat type.
-- @realm server
-- @player speaker Player who is speaking
-- @string chatType Name of the chat type
-- @string text Message to send
-- @bool[opt=false] bAnonymous Whether or not the speaker should be anonymous
-- @tab[opt=nil] receivers The players to replicate send the message to
-- @tab[opt=nil] data Additional data for this chat message
function ix.chat.Send(speaker, chatType, text, bAnonymous, receivers, data)
if (!chatType) then
return
end
data = data or {}
chatType = string.lower(chatType)
if (IsValid(speaker) and hook.Run("PrePlayerMessageSend", speaker, chatType, text, bAnonymous) == false) then
return
end
local class = ix.chat.classes[chatType]
if (class and class:CanSay(speaker, text, data) != false) then
if (class.CanHear and !receivers) then
receivers = {}
for _, v in player.Iterator() do
if (v:GetCharacter() and class:CanHear(speaker, v, data) != false) then
receivers[#receivers + 1] = v
end
end
if (#receivers == 0) then
return
end
end
-- Format the message if needed before we run the hook.
local rawText = text
local maxLength = ix.config.Get("chatMax")
-- Trim the text and remove extra spaces.
text = string.gsub(text, "%s+", " ")
if (text:utf8len() > maxLength) then
text = text:utf8sub(0, maxLength)
end
if (ix.config.Get("chatAutoFormat") and hook.Run("CanAutoFormatMessage", speaker, chatType, text)) then
text = ix.chat.Format(text)
end
text = hook.Run("PlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, rawText) or text
net.Start("ixChatMessage")
net.WriteEntity(speaker)
net.WriteString(chatType)
net.WriteString(text)
net.WriteBool(bAnonymous or false)
net.WriteTable(data or {})
net.Send(receivers)
return text
end
end
else
function ix.chat.Send(speaker, chatType, text, anonymous, data)
local class = ix.chat.classes[chatType]
if (class) then
-- Trim the text and remove extra spaces.
text = string.gsub(text, "%s+", " ")
-- luacheck: globals CHAT_CLASS
CHAT_CLASS = class
class:OnChatAdd(speaker, text, anonymous, data)
CHAT_CLASS = nil
end
end
-- Call OnChatAdd for the appropriate chatType.
net.Receive("ixChatMessage", function()
local client = net.ReadEntity()
local chatType = net.ReadString()
local text = net.ReadString()
local anonymous = net.ReadBool()
local data = net.ReadTable()
if (IsValid(client)) then
local info = {
chatType = chatType,
text = text,
anonymous = anonymous,
data = data
}
hook.Run("MessageReceived", client, info)
ix.chat.Send(client, info.chatType or chatType, info.text or text, info.anonymous or anonymous, info.data)
else
ix.chat.Send(nil, chatType, text, anonymous, data)
end
end)
end
-- Add the default chat types here.
do
-- Load the chat types after the configs so we can access changed configs.
hook.Add("InitializedConfig", "ixChatTypes", function()
-- The default in-character chat.
ix.chat.Register("ic", {
format = "%s says \"%s\"",
indicator = "chatTalking",
GetColor = function(self, speaker, text)
-- If you are looking at the speaker, make it greener to easier identify who is talking.
if (LocalPlayer():GetEyeTrace().Entity == speaker) then
return ix.config.Get("chatListenColor")
end
-- Otherwise, use the normal chat color.
return ix.config.Get("chatColor")
end,
CanHear = ix.config.Get("chatRange", 280)
})
-- Actions and such.
ix.chat.Register("me", {
format = "** %s %s",
GetColor = ix.chat.classes.ic.GetColor,
CanHear = ix.config.Get("chatRange", 280) * 2,
prefix = {"/Me", "/Action"},
description = "@cmdMe",
indicator = "chatPerforming",
deadCanChat = true
})
-- Actions and such.
ix.chat.Register("it", {
OnChatAdd = function(self, speaker, text)
chat.AddText(ix.config.Get("chatColor"), "** "..text)
end,
CanHear = ix.config.Get("chatRange", 280) * 2,
prefix = {"/It"},
description = "@cmdIt",
indicator = "chatPerforming",
deadCanChat = true
})
-- Whisper chat.
ix.chat.Register("w", {
format = "%s whispers \"%s\"",
GetColor = function(self, speaker, text)
local color = ix.chat.classes.ic:GetColor(speaker, text)
-- Make the whisper chat slightly darker than IC chat.
return Color(color.r - 35, color.g - 35, color.b - 35)
end,
CanHear = ix.config.Get("chatRange", 280) * 0.25,
prefix = {"/W", "/Whisper"},
description = "@cmdW",
indicator = "chatWhispering"
})
-- Yelling out loud.
ix.chat.Register("y", {
format = "%s yells \"%s\"",
GetColor = function(self, speaker, text)
local color = ix.chat.classes.ic:GetColor(speaker, text)
-- Make the yell chat slightly brighter than IC chat.
return Color(color.r + 35, color.g + 35, color.b + 35)
end,
CanHear = ix.config.Get("chatRange", 280) * 2,
prefix = {"/Y", "/Yell"},
description = "@cmdY",
indicator = "chatYelling"
})
-- Out of character.
ix.chat.Register("ooc", {
CanSay = function(self, speaker, text)
if (!ix.config.Get("allowGlobalOOC")) then
speaker:NotifyLocalized("Global OOC is disabled on this server.")
return false
else
local delay = ix.config.Get("oocDelay", 10)
-- Only need to check the time if they have spoken in OOC chat before.
if (delay > 0 and speaker.ixLastOOC) then
local lastOOC = CurTime() - speaker.ixLastOOC
-- Use this method of checking time in case the oocDelay config changes.
if (lastOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then
speaker:NotifyLocalized("oocDelay", delay - math.ceil(lastOOC))
return false
end
end
-- Save the last time they spoke in OOC.
speaker.ixLastOOC = CurTime()
end
end,
OnChatAdd = function(self, speaker, text)
-- @todo remove and fix actual cause of speaker being nil
if (!IsValid(speaker)) then
return
end
local icon = "icon16/user.png"
if (speaker:IsSuperAdmin()) then
icon = "icon16/shield.png"
elseif (speaker:IsAdmin()) then
icon = "icon16/star.png"
elseif (speaker:IsUserGroup("moderator") or speaker:IsUserGroup("operator")) then
icon = "icon16/wrench.png"
elseif (speaker:IsUserGroup("vip") or speaker:IsUserGroup("donator") or speaker:IsUserGroup("donor")) then
icon = "icon16/heart.png"
end
icon = Material(hook.Run("GetPlayerIcon", speaker) or icon)
chat.AddText(icon, Color(255, 50, 50), "[OOC] ", speaker, color_white, ": "..text)
end,
prefix = {"//", "/OOC"},
description = "@cmdOOC",
noSpaceAfter = true
})
-- Local out of character.
ix.chat.Register("looc", {
CanSay = function(self, speaker, text)
local delay = ix.config.Get("loocDelay", 0)
-- Only need to check the time if they have spoken in OOC chat before.
if (delay > 0 and speaker.ixLastLOOC) then
local lastLOOC = CurTime() - speaker.ixLastLOOC
-- Use this method of checking time in case the oocDelay config changes.
if (lastLOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then
speaker:NotifyLocalized("loocDelay", delay - math.ceil(lastLOOC))
return false
end
end
-- Save the last time they spoke in OOC.
speaker.ixLastLOOC = CurTime()
end,
OnChatAdd = function(self, speaker, text)
chat.AddText(Color(255, 50, 50), "[LOOC] ", ix.config.Get("chatColor"), speaker:Name()..": "..text)
end,
CanHear = ix.config.Get("chatRange", 280),
prefix = {".//", "[[", "/LOOC"},
description = "@cmdLOOC",
noSpaceAfter = true
})
-- Roll information in chat.
ix.chat.Register("roll", {
format = "** %s has rolled %s out of %s.",
color = Color(155, 111, 176),
CanHear = ix.config.Get("chatRange", 280),
deadCanChat = true,
OnChatAdd = function(self, speaker, text, bAnonymous, data)
local max = data.max or 100
local translated = L2(self.uniqueID.."Format", speaker:Name(), text, max)
chat.AddText(self.color, translated and "** "..translated or string.format(self.format,
speaker:Name(), text, max
))
end
})
-- run a hook after we add the basic chat classes so schemas/plugins can access their info as soon as possible if needed
hook.Run("InitializedChatClasses")
end)
end
-- Private messages between players.
ix.chat.Register("pm", {
format = "[PM] %s -> %s: %s",
color = Color(125, 150, 75, 255),
deadCanChat = true,
OnChatAdd = function(self, speaker, text, bAnonymous, data)
chat.AddText(self.color, string.format(self.format, speaker:GetName(), data.target:GetName(), text))
if (LocalPlayer() != speaker) then
surface.PlaySound("hl1/fvox/bell.wav")
end
end
})
-- Global events.
ix.chat.Register("event", {
CanHear = 1000000,
OnChatAdd = function(self, speaker, text)
chat.AddText(Color(255, 150, 0), text)
end,
indicator = "chatPerforming"
})
ix.chat.Register("connect", {
CanSay = function(self, speaker, text)
return !IsValid(speaker)
end,
OnChatAdd = function(self, speaker, text)
local icon = ix.util.GetMaterial("icon16/user_add.png")
chat.AddText(icon, Color(150, 150, 200), L("playerConnected", text))
end,
noSpaceAfter = true
})
ix.chat.Register("disconnect", {
CanSay = function(self, speaker, text)
return !IsValid(speaker)
end,
OnChatAdd = function(self, speaker, text)
local icon = ix.util.GetMaterial("icon16/user_delete.png")
chat.AddText(icon, Color(200, 150, 200), L("playerDisconnected", text))
end,
noSpaceAfter = true
})
ix.chat.Register("notice", {
CanSay = function(self, speaker, text)
return !IsValid(speaker)
end,
OnChatAdd = function(self, speaker, text, bAnonymous, data)
local icon = ix.util.GetMaterial(data.bError and "icon16/comment_delete.png" or "icon16/comment.png")
chat.AddText(icon, data.bError and Color(200, 175, 200, 255) or Color(175, 200, 255), text)
end,
noSpaceAfter = true
})
-- Why does ULX even have a /me command?
hook.Remove("PlayerSay", "ULXMeCheck")
================================================
FILE: gamemode/core/libs/sh_class.lua
================================================
--[[--
Helper library for loading/getting class information.
Classes are temporary assignments for characters - analogous to a "job" in a faction. For example, you may have a police faction
in your schema, and have "police recruit" and "police chief" as different classes in your faction. Anyone can join a class in
their faction by default, but you can restrict this as you need with `CLASS.CanSwitchTo`.
]]
-- @module ix.class
--- Class definition table returned by `ix.class.Get`.
-- @realm shared
-- @tab class Class definition table.
-- Default keys:
--
-- - `index` (number) - numeric class ID
-- - `uniqueID` (string) - stable identifier (e.g. `citizen`)
-- - `name` (string)
-- - `description` (string)
-- - `faction` (number) - Faction ID this class belongs to
-- - `isDefault` (boolean) - whether it is the default class for the faction
-- - `limit` (number) - maximum number of players allowed in this class (0 for unlimited)
-- - `CanSwitchTo` (function|nil) - optional callback to check if a player may switch
if (SERVER) then
util.AddNetworkString("ixClassUpdate")
end
ix.class = ix.class or {}
ix.class.list = {}
local charMeta = ix.meta.character
--- Loads classes from a directory.
-- @realm shared
-- @internal
-- @string directory The path to the class files.
function ix.class.LoadFromDir(directory)
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
-- Get the name without the "sh_" prefix and ".lua" suffix.
local niceName = v:sub(4, -5)
-- Determine a numeric identifier for this class.
local index = #ix.class.list + 1
local halt
for _, v2 in ipairs(ix.class.list) do
if (v2.uniqueID == niceName) then
halt = true
break
end
end
if (halt == true) then
continue
end
-- Set up a global table so the file has access to the class table.
CLASS = {index = index, uniqueID = niceName}
CLASS.name = "Unknown"
CLASS.description = "No description available."
CLASS.limit = 0
-- For future use with plugins.
if (PLUGIN) then
CLASS.plugin = PLUGIN.uniqueID
end
ix.util.Include(directory.."/"..v, "shared")
-- Why have a class without a faction?
if (!CLASS.faction or !team.Valid(CLASS.faction)) then
ErrorNoHalt("Class '"..niceName.."' does not have a valid faction!\n")
CLASS = nil
continue
end
-- Allow classes to be joinable by default.
if (!CLASS.CanSwitchTo) then
CLASS.CanSwitchTo = function(client)
return true
end
end
ix.class.list[index] = CLASS
CLASS = nil
end
end
--- Determines if a player is allowed to join a specific class.
-- @realm shared
-- @player client Player to check
-- @number class Index of the class
-- @treturn bool Whether or not the player can switch to the class.
-- @treturn string The reason why the player cannot switch (if applicable).
-- @usage -- Check if a player can join class ID 2.
-- -- For our example, they can't- because they are in the wrong faction.
-- local canJoin, reason = ix.class.CanSwitchTo(player, 2)
-- if (!canJoin) then
-- print("Player cannot join class: "..reason)
-- end
-- > Player cannot join class: not correct team
function ix.class.CanSwitchTo(client, class)
-- Get the class table by its numeric identifier.
local info = ix.class.list[class]
-- See if the class exists.
if (!info) then
return false, "no info"
end
-- If the player's faction matches the class's faction.
if (client:Team() != info.faction) then
return false, "not correct team"
end
if (client:GetCharacter():GetClass() == class) then
return false, "same class request"
end
if (info.limit > 0) then
if (#ix.class.GetPlayers(info.index) >= info.limit) then
return false, "class is full"
end
end
if (hook.Run("CanPlayerJoinClass", client, class, info) == false) then
return false
end
-- See if the class allows the player to join it.
return info:CanSwitchTo(client)
end
--- Gets a class definition table by numeric identifier.
-- @realm shared
-- @tparam number identifier Numeric class identifier.
-- @treturn table|nil Class definition table (see `class` table docs).
-- @usage -- Print the name of class ID 1
-- print(ix.class.Get(1).name)
-- > Citizen
function ix.class.Get(identifier)
return ix.class.list[identifier]
end
--- Retrieves all players currently assigned to a specific class.
-- @realm shared
-- @number class Index of the class
-- @treturn table Numerically indexed table of players in the class, or an empty table if none are found.
-- @usage -- Print all players in class ID 1
-- for _, ply in ipairs(ix.class.GetPlayers(1)) do
-- print(ply:GetName())
-- end
-- > Player1
-- > Player2
-- > etc
function ix.class.GetPlayers(class)
local players = {}
for _, v in player.Iterator() do
local char = v:GetCharacter()
if (char and char:GetClass() == class) then
table.insert(players, v)
end
end
return players
end
if (SERVER) then
--- Character class methods
-- @classmod Character
--- Makes this character join a class. This automatically calls `KickClass` for you.
-- @realm server
-- @number class Index of the class to join
-- @treturn bool Whether or not the character has successfully joined the class
function charMeta:JoinClass(class)
if (!class) then
self:KickClass()
return false
end
local oldClass = self:GetClass()
local client = self:GetPlayer()
if (ix.class.CanSwitchTo(client, class)) then
self:SetClass(class)
hook.Run("PlayerJoinedClass", client, class, oldClass)
return true
end
return false
end
--- Kicks this character out of the class they are currently in.
-- @realm server
function charMeta:KickClass()
local client = self:GetPlayer()
if (!client) then return end
local goClass
for k, v in pairs(ix.class.list) do
if (v.faction == client:Team() and v.isDefault) then
goClass = k
break
end
end
if (!goClass) then
ErrorNoHaltWithStack("[Helix] No default class set for faction '" .. team.GetName(client:Team()) .. "'")
return
end
self:JoinClass(goClass)
hook.Run("PlayerJoinedClass", client, goClass)
end
function GM:PlayerJoinedClass(client, class, oldClass)
local info = ix.class.list[class]
local info2 = ix.class.list[oldClass]
if (info.OnSet) then
info:OnSet(client)
end
if (info2 and info2.OnLeave) then
info2:OnLeave(client)
end
net.Start("ixClassUpdate")
net.WriteEntity(client)
net.Broadcast()
end
end
================================================
FILE: gamemode/core/libs/sh_command.lua
================================================
--[[--
Registration, parsing, and handling of commands.
Commands can be ran through the chat with slash commands or they can be executed through the console. Commands can be manually
restricted to certain usergroups using a [CAMI](https://github.com/glua/CAMI)-compliant admin mod.
]]
-- @module ix.command
--- When registering commands with `ix.command.Add`, you'll need to pass in a valid command structure. This is simply a table
-- with various fields defined to describe the functionality of the command.
-- @realm shared
-- @table CommandStructure
-- @field[type=function] OnRun This function is called when the command has passed all the checks and can execute. The first two
-- arguments will be the running command table and the calling player. If the arguments field has been specified, the arguments
-- will be passed as regular function parameters rather than in a table.
--
-- When the arguments field is defined: `OnRun(self, client, target, length, message)`
--
-- When the arguments field is NOT defined: `OnRun(self, client, arguments)`
-- @field[type=string,opt="@noDesc"] description The help text that appears when the user types in the command. If the string is
-- prefixed with `"@"`, it will use a language phrase.
-- @field[type=table,opt=nil] argumentNames An array of strings corresponding to each argument of the command. This ignores the
-- name that's specified in the `OnRun` function arguments and allows you to use any string to change the text that displays
-- in the command's syntax help. When using this field, make sure that the amount is equal to the amount of arguments, as such:
-- COMMAND.arguments = {ix.type.character, ix.type.number}
-- COMMAND.argumentNames = {"target char", "cash (1-1000)"}
-- @field[type=table,opt] arguments If this field is defined, then additional checks will be performed to ensure that the
-- arguments given to the command are valid. This removes extra boilerplate code since all the passed arguments are guaranteed
-- to be valid. See `CommandArgumentsStructure` for more information.
-- @field[type=boolean,opt=false] adminOnly Provides an additional check to see if the user is an admin before running.
-- @field[type=boolean,opt=false] superAdminOnly Provides an additional check to see if the user is a superadmin before running.
-- @field[type=string,opt=nil] privilege Manually specify a privilege name for this command. It will always be prefixed with
-- `"Helix - "`. This is used in the case that you want to group commands under the same privilege, or use a privilege that
-- you've already defined (i.e grouping `/CharBan` and `/CharUnban` into the `Helix - Ban Character` privilege).
-- @field[type=function,opt=nil] OnCheckAccess This callback checks whether or not the player is allowed to run the command.
-- This callback should **NOT** be used in conjunction with `adminOnly` or `superAdminOnly`, as populating those
-- fields create a custom a `OnCheckAccess` callback for you internally. This is used in cases where you want more fine-grained
-- access control for your command.
--
-- Keep in mind that this is a **SHARED** callback; the command will not show up the client if the callback returns `false`.
--- Rather than checking the validity for arguments in your command's `OnRun` function, you can have Helix do it for you to
-- reduce the amount of boilerplate code that needs to be written. This can be done by populating the `arguments` field.
--
-- When using the `arguments` field in your command, you are specifying specific types that you expect to receive when the
-- command is ran successfully. This means that before `OnRun` is called, the arguments passed to the command from a user will
-- be verified to be valid. Each argument is an `ix.type` entry that specifies the expected type for that argument. Optional
-- arguments can be specified by using a bitwise OR with the special `ix.type.optional` type. When specified as optional, the
-- argument can be `nil` if the user has not entered anything for that argument - otherwise it will be valid.
--
-- Note that optional arguments must always be at the end of a list of arguments - or rather, they must not follow a required
-- argument. The `syntax` field will be automatically populated when using strict arguments, which means you shouldn't fill out
-- the `syntax` field yourself. The arguments you specify will have the same names as the arguments in your OnRun function.
--
-- Consider this example command:
-- ix.command.Add("CharSlap", {
-- description = "Slaps a character with a large trout.",
-- adminOnly = true,
-- arguments = {
-- ix.type.character,
-- bit.bor(ix.type.number, ix.type.optional)
-- },
-- OnRun = function(self, client, target, damage)
-- -- WHAM!
-- end
-- })
-- Here, we've specified the first argument called `target` to be of type `character`, and the second argument called `damage`
-- to be of type `number`. The `damage` argument is optional, meaning that the command will still run if the user has not
-- specified any value for the damage. In this case, we'll need to check if it was specified by doing a simple
-- `if (damage) then`. The syntax field will be automatically populated with the value `" [damage: number]"`.
-- @realm shared
-- @table CommandArgumentsStructure
ix.command = ix.command or {}
ix.command.list = ix.command.list or {}
local COMMAND_PREFIX = "/"
local function ArgumentCheckStub(command, client, given)
local arguments = command.arguments
local result = {}
for i = 1, #arguments do
local bOptional = bit.band(arguments[i], ix.type.optional) == ix.type.optional
local argType = bOptional and bit.bxor(arguments[i], ix.type.optional) or arguments[i]
local argument = given[i]
if (!argument and !bOptional) then
return L("invalidArg", client, i)
end
if (argType == ix.type.string) then
if (!argument and bOptional) then
result[#result + 1] = nil
else
result[#result + 1] = tostring(argument)
end
elseif (argType == ix.type.text) then
result[#result + 1] = table.concat(given, " ", i) or ""
break
elseif (argType == ix.type.number) then
local value = tonumber(argument)
if (!bOptional and !value) then
return L("invalidArg", client, i)
end
result[#result + 1] = value
elseif (argType == ix.type.player or argType == ix.type.character) then
local bPlayer = argType == ix.type.player
local value = ix.util.FindPlayer(argument or "") -- argument could be nil due to optional type
-- FindPlayer emits feedback for us
if (!value and !bOptional) then
return L(bPlayer and "plyNoExist" or "charNoExist", client)
end
-- check for the character if we're using the character type
if (!bPlayer) then
local character = value:GetCharacter()
if (!character) then
return L("charNoExist", client)
end
value = character
end
result[#result + 1] = value
elseif (argType == ix.type.steamid) then
local value = argument:match("STEAM_(%d+):(%d+):(%d+)")
if (!value and bOptional) then
return L("invalidArg", client, i)
end
result[#result + 1] = value
elseif (argType == ix.type.bool) then
if (argument == nil and bOptional) then
result[#result + 1] = nil
else
result[#result + 1] = tobool(argument)
end
end
end
return result
end
--- Creates a new command.
-- @realm shared
-- @string command Name of the command (recommended in UpperCamelCase)
-- @tparam CommandStructure data Data describing the command
-- @see CommandStructure
-- @see CommandArgumentsStructure
function ix.command.Add(command, data)
data.name = string.gsub(command, "%s", "")
data.description = data.description or ""
command = command:lower()
data.uniqueID = command
-- Why bother adding a command if it doesn't do anything.
if (!data.OnRun) then
return ErrorNoHalt("Command '"..command.."' does not have a callback, not adding!\n")
end
-- Add a function to get the description that can be overridden.
if (!data.GetDescription) then
-- Check if the description is using a language string.
if (data.description:sub(1, 1) == "@") then
function data:GetDescription()
return L(self.description:sub(2))
end
else
-- Otherwise just return the raw description.
function data:GetDescription()
return self.description
end
end
end
-- OnCheckAccess by default will rely on CAMI for access information with adminOnly/superAdminOnly being fallbacks
if (!data.OnCheckAccess) then
if (data.group) then
ErrorNoHalt("Command '" .. data.name .. "' tried to use the deprecated field 'group'!\n")
return
end
local privilege = "Helix - " .. (isstring(data.privilege) and data.privilege or data.name)
-- we could be using a previously-defined privilege
if (!CAMI.GetPrivilege(privilege)) then
CAMI.RegisterPrivilege({
Name = privilege,
MinAccess = data.superAdminOnly and "superadmin" or (data.adminOnly and "admin" or "user"),
Description = data.description
})
end
function data:OnCheckAccess(client)
local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil)
return bHasAccess
end
end
-- if we have an arguments table, then we're using the new command format
if (data.arguments) then
local bFirst = true
local bLastOptional = false
local bHasArgumentNames = istable(data.argumentNames)
data.syntax = "" -- @todo deprecate this in favour of argumentNames
data.argumentNames = bHasArgumentNames and data.argumentNames or {}
-- if one argument is supplied by itself, put it into a table
if (!istable(data.arguments)) then
data.arguments = {data.arguments}
end
if (bHasArgumentNames and #data.argumentNames != #data.arguments) then
return ErrorNoHalt(string.format(
"Command '%s' doesn't have argument names that correspond to each argument\n", command
))
end
-- check the arguments table to see if its entries are valid
for i = 1, #data.arguments do
local argument = data.arguments[i]
local argumentName = debug.getlocal(data.OnRun, 2 + i)
if (argument == ix.type.optional) then
return ErrorNoHalt(string.format(
"Command '%s' tried to use an optional argument for #%d without specifying type\n", command, i
))
elseif (!isnumber(argument)) then
return ErrorNoHalt(string.format(
"Command '%s' tried to use an invalid type for argument #%d\n", command, i
))
elseif (argument == ix.type.array or bit.band(argument, ix.type.array) > 0) then
return ErrorNoHalt(string.format(
"Command '%s' tried to use an unsupported type 'array' for argument #%d\n", command, i
))
end
local bOptional = bit.band(argument, ix.type.optional) > 0
argument = bOptional and bit.bxor(argument, ix.type.optional) or argument
if (!ix.type[argument]) then
return ErrorNoHalt(string.format(
"Command '%s' tried to use an invalid type for argument #%d\n", command, i
))
elseif (!isstring(argumentName)) then
return ErrorNoHalt(string.format(
"Command '%s' is missing function argument for command argument #%d\n", command, i
))
elseif (argument == ix.type.text and i != #data.arguments) then
return ErrorNoHalt(string.format(
"Command '%s' tried to use a text argument outside of the last argument\n", command
))
elseif (!bOptional and bLastOptional) then
return ErrorNoHalt(string.format(
"Command '%s' tried to use an required argument after an optional one\n", command
))
end
-- text is always optional and will return an empty string if nothing is specified, rather than nil
if (argument == ix.type.text) then
data.arguments[i] = bit.bor(ix.type.text, ix.type.optional)
bOptional = true
end
if (!bHasArgumentNames) then
data.argumentNames[i] = argumentName
end
data.syntax = data.syntax .. (bFirst and "" or " ") ..
string.format((bOptional and "[%s: %s]" or "<%s: %s>"), argumentName, ix.type[argument])
bFirst = false
bLastOptional = bOptional
end
if (data.syntax:utf8len() == 0) then
data.syntax = ""
end
else
data.syntax = data.syntax or ""
end
-- Add the command to the list of commands.
local alias = data.alias
if (alias) then
if (istable(alias)) then
for _, v in ipairs(alias) do
ix.command.list[v:lower()] = data
end
elseif (isstring(alias)) then
ix.command.list[alias:lower()] = data
end
end
ix.command.list[command] = data
end
--- Returns true if a player is allowed to run a certain command.
-- @realm shared
-- @player client Player to check access for
-- @string command Name of the command to check access for
-- @treturn bool Whether or not the player is allowed to run the command
function ix.command.HasAccess(client, command)
command = ix.command.list[command:lower()]
if (command) then
if (command.OnCheckAccess) then
return command:OnCheckAccess(client)
else
return true
end
end
return false
end
--- Returns a table of arguments from a given string.
-- Words separated by spaces will be considered one argument. To have an argument containing multiple words, they must be
-- contained within quotation marks.
-- @realm shared
-- @string text String to extract arguments from
-- @treturn table Arguments extracted from string
-- @usage PrintTable(ix.command.ExtractArgs("these are \"some arguments\""))
-- > 1 = these
-- > 2 = are
-- > 3 = some arguments
function ix.command.ExtractArgs(text)
local skip = 0
local arguments = {}
local curString = ""
for i = 1, text:utf8len() do
if (i <= skip) then continue end
local c = text:utf8sub(i, i)
if (c == "\"") then
local match = text:utf8sub(i):match("%b\"\"")
if (match) then
curString = ""
skip = i + match:utf8len()
arguments[#arguments + 1] = match:utf8sub(2, -2)
else
curString = curString..c
end
elseif (c == " " and curString != "") then
arguments[#arguments + 1] = curString
curString = ""
else
if (c == " " and curString == "") then
continue
end
curString = curString..c
end
end
if (curString != "") then
arguments[#arguments + 1] = curString
end
return arguments
end
--- Returns an array of potential commands by unique id.
-- When bSorted is true, the commands will be sorted by name. When bReorganize is true, it will move any exact match to the top
-- of the array. When bRemoveDupes is true, it will remove any commands that have the same NAME.
-- @realm shared
-- @string identifier Search query
-- @bool[opt=false] bSorted Whether or not to sort the commands by name
-- @bool[opt=false] bReorganize Whether or not any exact match will be moved to the top of the array
-- @bool[opt=false] bRemoveDupes Whether or not to remove any commands that have the same name
-- @treturn table Array of command tables whose name partially or completely matches the search query
function ix.command.FindAll(identifier, bSorted, bReorganize, bRemoveDupes)
local result = {}
local iterator = bSorted and SortedPairs or pairs
local fullMatch
identifier = identifier:lower()
if (identifier == "/") then
-- we don't simply copy because we need numeric indices
for _, v in iterator(ix.command.list) do
result[#result + 1] = v
end
return result
elseif (identifier:utf8sub(1, 1) == "/") then
identifier = identifier:utf8sub(2)
end
for k, v in iterator(ix.command.list) do
if (k:match(identifier)) then
local index = #result + 1
result[index] = v
if (k == identifier) then
fullMatch = index
end
end
end
if (bReorganize and fullMatch and fullMatch != 1) then
result[1], result[fullMatch] = result[fullMatch], result[1]
end
if (bRemoveDupes) then
local commandNames = {}
-- using pairs intead of ipairs because we might remove from array
for k, v in pairs(result) do
if (commandNames[v.name]) then
table.remove(result, k)
end
commandNames[v.name] = true
end
end
return result
end
if (SERVER) then
util.AddNetworkString("ixCommand")
--- Attempts to find a player by an identifier. If unsuccessful, a notice will be displayed to the specified player. The
-- search criteria is derived from `ix.util.FindPlayer`.
-- @realm server
-- @player client Player to give a notification to if the player could not be found
-- @string name Search query
-- @treturn[1] player Player that matches the given search query
-- @treturn[2] nil If a player could not be found
-- @see ix.util.FindPlayer
function ix.command.FindPlayer(client, name)
local target = isstring(name) and ix.util.FindPlayer(name) or NULL
if (IsValid(target)) then
return target
else
client:NotifyLocalized("plyNoExist")
end
end
--- Forces a player to execute a command by name.
-- @realm server
-- @player client Player who is executing the command
-- @string command Full name of the command to be executed. This string gets lowered, but it's good practice to stick with
-- the exact name of the command
-- @tab arguments Array of arguments to be passed to the command
-- @usage ix.command.Run(player.GetByID(1), "Roll", {10})
function ix.command.Run(client, command, arguments)
if ((client.ixCommandCooldown or 0) > RealTime()) then
return
end
command = ix.command.list[tostring(command):lower()]
if (!command) then
return
end
-- we throw it into a table since arguments get unpacked and only
-- the arguments table gets passed in by default
local argumentsTable = arguments
arguments = {argumentsTable}
-- if feedback is non-nil, we can assume that the command failed
-- and is a phrase string
local feedback
-- check for group access
if (command.OnCheckAccess) then
local bSuccess, phrase = command:OnCheckAccess(client)
feedback = !bSuccess and L(phrase and phrase or "noPerm", client) or nil
end
-- check for strict arguments
if (!feedback and command.arguments) then
arguments = ArgumentCheckStub(command, client, argumentsTable)
if (isstring(arguments)) then
feedback = arguments
end
end
-- run the command if all the checks passed
if (!feedback) then
local results = {command:OnRun(client, unpack(arguments))}
local phrase = results[1]
-- check to see if the command has returned a phrase string and display it
if (isstring(phrase)) then
if (IsValid(client)) then
if (phrase:sub(1, 1) == "@") then
client:NotifyLocalized(phrase:sub(2), unpack(results, 2))
else
client:Notify(phrase)
end
else
-- print message since we're running from the server console
print(phrase)
end
end
client.ixCommandCooldown = RealTime() + 0.5
if (IsValid(client)) then
ix.log.Add(client, "command", COMMAND_PREFIX .. command.name, argumentsTable and table.concat(argumentsTable, " "))
end
else
client:Notify(feedback)
end
end
--- Parses a chat string and runs the command if one is found. Specifically, it checks for commands in a string with the
-- format `/CommandName some arguments`
-- @realm server
-- @player client Player who is executing the command
-- @string text Input string to search for the command format
-- @string[opt] realCommand Specific command to check for. If this is specified, it will not try to run any command that's
-- found at the beginning - only if it matches `realCommand`
-- @tab[opt] arguments Array of arguments to pass to the command. If not specified, it will try to extract it from the
-- string specified in `text` using `ix.command.ExtractArgs`
-- @treturn bool Whether or not a command has been found
-- @usage ix.command.Parse(player.GetByID(1), "/roll 10")
function ix.command.Parse(client, text, realCommand, arguments)
if (realCommand or text:utf8sub(1, 1) == COMMAND_PREFIX) then
-- See if the string contains a command.
local match = realCommand or text:utf8lower():match(COMMAND_PREFIX.."([_%w]+)")
-- is it unicode text?
if (!match) then
local post = string.Explode(" ", text)
local len = string.len(post[1])
match = post[1]:utf8sub(2, len)
end
match = match:utf8lower()
local command = ix.command.list[match]
-- We have a valid, registered command.
if (command) then
-- Get the arguments like a console command.
if (!arguments) then
arguments = ix.command.ExtractArgs(text:utf8sub(match:utf8len() + 3))
end
-- Runs the actual command.
ix.command.Run(client, match, arguments)
else
if (IsValid(client)) then
client:NotifyLocalized("cmdNoExist")
else
print("Sorry, that command does not exist.")
end
end
return true
end
return false
end
concommand.Add("ix", function(client, _, arguments)
local command = arguments[1]
table.remove(arguments, 1)
ix.command.Parse(client, nil, command or "", arguments)
end)
net.Receive("ixCommand", function(length, client)
if ((client.ixNextCmd or 0) < CurTime()) then
local command = net.ReadString()
local indices = net.ReadUInt(4)
local arguments = {}
for _ = 1, indices do
local value = net.ReadType()
if (isstring(value) or isnumber(value)) then
arguments[#arguments + 1] = tostring(value)
end
end
ix.command.Parse(client, nil, command, arguments)
client.ixNextCmd = CurTime() + 0.2
end
end)
else
--- Request the server to run a command. This mimics similar functionality to the client typing `/CommandName` in the chatbox.
-- @realm client
-- @string command Unique ID of the command
-- @param ... Arguments to pass to the command
-- @usage ix.command.Send("roll", 10)
function ix.command.Send(command, ...)
local arguments = {...}
net.Start("ixCommand")
net.WriteString(command)
net.WriteUInt(#arguments, 4)
for _, v in ipairs(arguments) do
net.WriteType(v)
end
net.SendToServer()
end
concommand.Add("ix", function(client, _, arguments)
ix.command.Send(table.remove(arguments, 1), unpack(arguments))
end, function(_, arguments)
arguments = arguments:TrimLeft()
local autocomplete = {}
local command = string.Explode(" ", arguments)[1]
for _, v in pairs(ix.command.FindAll(command, true, true)) do
if (v.OnCheckAccess and !v:OnCheckAccess(LocalPlayer())) then
continue
end
if (arguments:find(v.uniqueID, 1, true) == 1) then
return {
"ix " .. arguments,
v:GetDescription(),
L("syntax", v.syntax)
}
end
table.insert(autocomplete, "ix " .. v.uniqueID)
end
return autocomplete
end)
end
================================================
FILE: gamemode/core/libs/sh_currency.lua
================================================
--- A library representing the server's currency system.
-- @module ix.currency
ix.currency = ix.currency or {}
ix.currency.symbol = ix.currency.symbol or "$"
ix.currency.singular = ix.currency.singular or "dollar"
ix.currency.plural = ix.currency.plural or "dollars"
ix.currency.model = ix.currency.model or "models/props_lab/box01a.mdl"
--- Sets the currency type.
-- @realm shared
-- @string symbol The symbol of the currency.
-- @string singular The name of the currency in it's singular form.
-- @string plural The name of the currency in it's plural form.
-- @string model The model of the currency entity.
function ix.currency.Set(symbol, singular, plural, model)
ix.currency.symbol = symbol
ix.currency.singular = singular
ix.currency.plural = plural
ix.currency.model = model
end
--- Returns a formatted string according to the current currency.
-- @realm shared
-- @number amount The amount of cash being formatted.
-- @treturn string The formatted string.
function ix.currency.Get(amount)
if (amount == 1) then
return ix.currency.symbol.."1 "..ix.currency.singular
else
return ix.currency.symbol..amount.." "..ix.currency.plural
end
end
--- Spawns an amount of cash at a specific location on the map.
-- @realm shared
-- @vector pos The position of the money to be spawned.
-- @number amount The amount of cash being spawned.
-- @angle[opt=angle_zero] angle The angle of the entity being spawned.
-- @treturn entity The spawned money entity.
function ix.currency.Spawn(pos, amount, angle)
if (!amount or amount < 0) then
print("[Helix] Can't create currency entity: Invalid Amount of money")
return
end
local money = ents.Create("ix_money")
money:Spawn()
if (IsValid(pos) and pos:IsPlayer()) then
pos = pos:GetItemDropPos(money)
elseif (!isvector(pos)) then
print("[Helix] Can't create currency entity: Invalid Position")
money:Remove()
return
end
money:SetPos(pos)
-- double check for negative.
money:SetAmount(math.Round(math.abs(amount)))
money:SetAngles(angle or angle_zero)
money:Activate()
return money
end
function GM:OnPickupMoney(client, moneyEntity)
if (IsValid(moneyEntity)) then
local amount = moneyEntity:GetAmount()
client:GetCharacter():GiveMoney(amount)
end
end
do
local character = ix.meta.character
function character:HasMoney(amount)
if (amount < 0) then
print("Negative Money Check Received.")
end
return self:GetMoney() >= amount
end
function character:GiveMoney(amount, bNoLog)
amount = math.abs(amount)
if (!bNoLog) then
ix.log.Add(self:GetPlayer(), "money", amount)
end
self:SetMoney(self:GetMoney() + amount)
return true
end
function character:TakeMoney(amount, bNoLog)
amount = math.abs(amount)
if (!bNoLog) then
ix.log.Add(self:GetPlayer(), "money", -amount)
end
self:SetMoney(self:GetMoney() - amount)
return true
end
end
================================================
FILE: gamemode/core/libs/sh_date.lua
================================================
--[[--
Persistent date and time handling.
All of Lua's time functions are dependent on the Unix epoch, which means we can't have dates that go further than 1970. This
library remedies this problem. Time/date is represented by a `date` object that is queried, instead of relying on the seconds
since the epoch.
## Futher documentation
This library makes use of a third-party date library found at https://github.com/Tieske/date - you can find all documentation
regarding the `date` object and its methods there.
]]
-- @module ix.date
ix.date = ix.date or {}
ix.date.lib = ix.date.lib or include("thirdparty/sh_date.lua")
ix.date.timeScale = ix.date.timeScale or ix.config.Get("secondsPerMinute", 60) -- seconds per minute
ix.date.current = ix.date.current or ix.date.lib() -- current in-game date/time
ix.date.start = ix.date.start or CurTime() -- arbitrary start time for calculating date/time offset
if (SERVER) then
util.AddNetworkString("ixDateSync")
--- Loads the date from disk.
-- @realm server
-- @internal
function ix.date.Initialize()
local currentDate = ix.data.Get("date", nil, false, true)
-- construct new starting date if we don't have it saved already
if (!currentDate) then
currentDate = {
year = ix.config.Get("year"),
month = ix.config.Get("month"),
day = ix.config.Get("day"),
hour = tonumber(os.date("%H")) or 0,
min = tonumber(os.date("%M")) or 0,
sec = tonumber(os.date("%S")) or 0
}
currentDate = ix.date.lib.serialize(ix.date.lib(currentDate))
ix.data.Set("date", currentDate, false, true)
end
ix.date.timeScale = ix.config.Get("secondsPerMinute", 60)
ix.date.current = ix.date.lib.construct(currentDate)
end
--- Updates the internal in-game date/time representation and resets the offset.
-- @realm server
-- @internal
function ix.date.ResolveOffset()
ix.date.current = ix.date.Get()
ix.date.start = CurTime()
end
--- Updates the time scale of the in-game date/time. The time scale is given in seconds per minute (i.e how many real life
-- seconds it takes for an in-game minute to pass). You should avoid using this function and use the in-game config menu to
-- change the time scale instead.
-- @realm server
-- @internal
-- @number secondsPerMinute New time scale
function ix.date.UpdateTimescale(secondsPerMinute)
ix.date.ResolveOffset()
ix.date.timeScale = secondsPerMinute
end
--- Sends the current date to a player. This is done automatically when the player joins the server.
-- @realm server
-- @internal
-- @player[opt=nil] client Player to send the date to, or `nil` to send to everyone
function ix.date.Send(client)
net.Start("ixDateSync")
net.WriteFloat(ix.date.timeScale)
net.WriteTable(ix.date.current)
net.WriteFloat(ix.date.start)
if (client) then
net.Send(client)
else
net.Broadcast()
end
end
--- Saves the current in-game date to disk.
-- @realm server
-- @internal
function ix.date.Save()
ix.date.bSaving = true
ix.date.ResolveOffset() -- resolve offset so we save the actual time to disk
ix.data.Set("date", ix.date.lib.serialize(ix.date.current), false, true)
-- update config to reflect current saved date
ix.config.Set("year", ix.date.current:getyear())
ix.config.Set("month", ix.date.current:getmonth())
ix.config.Set("day", ix.date.current:getday())
ix.date.bSaving = nil
end
else
net.Receive("ixDateSync", function()
local timeScale = net.ReadFloat()
local currentDate = ix.date.lib.construct(net.ReadTable())
local startTime = net.ReadFloat()
ix.date.timeScale = timeScale
ix.date.current = currentDate
ix.date.start = startTime
end)
end
--- Returns the currently set date.
-- @realm shared
-- @treturn date Current in-game date
function ix.date.Get()
local minutesSinceStart = (CurTime() - ix.date.start) / ix.date.timeScale
return ix.date.current:copy():addminutes(minutesSinceStart)
end
--- Returns a string formatted version of a date.
-- @realm shared
-- @string format Format string
-- @date[opt=nil] currentDate Date to format. If nil, it will use the currently set date
-- @treturn string Formatted date
function ix.date.GetFormatted(format, currentDate)
return (currentDate or ix.date.Get()):fmt(format)
end
--- Returns a serialized version of a date. This is useful when you need to network a date to clients, or save a date to disk.
-- @realm shared
-- @date[opt=nil] currentDate Date to serialize. If nil, it will use the currently set date
-- @treturn table Serialized date
function ix.date.GetSerialized(currentDate)
return ix.date.lib.serialize(currentDate or ix.date.Get())
end
--- Returns a date object from a table or serialized date.
-- @realm shared
-- @param currentDate Date to construct
-- @treturn date Constructed date object
function ix.date.Construct(currentDate)
return ix.date.lib.construct(currentDate)
end
================================================
FILE: gamemode/core/libs/sh_faction.lua
================================================
--- Helper library for loading/getting faction information.
-- @module ix.faction
ix.faction = ix.faction or {}
ix.faction.teams = ix.faction.teams or {}
ix.faction.indices = ix.faction.indices or {}
local CITIZEN_MODELS = {
"models/humans/group01/male_01.mdl",
"models/humans/group01/male_02.mdl",
"models/humans/group01/male_04.mdl",
"models/humans/group01/male_05.mdl",
"models/humans/group01/male_06.mdl",
"models/humans/group01/male_07.mdl",
"models/humans/group01/male_08.mdl",
"models/humans/group01/male_09.mdl",
"models/humans/group02/male_01.mdl",
"models/humans/group02/male_03.mdl",
"models/humans/group02/male_05.mdl",
"models/humans/group02/male_07.mdl",
"models/humans/group02/male_09.mdl",
"models/humans/group01/female_01.mdl",
"models/humans/group01/female_02.mdl",
"models/humans/group01/female_03.mdl",
"models/humans/group01/female_06.mdl",
"models/humans/group01/female_07.mdl",
"models/humans/group02/female_01.mdl",
"models/humans/group02/female_03.mdl",
"models/humans/group02/female_06.mdl",
"models/humans/group01/female_04.mdl"
}
--- Loads factions from a directory.
-- @realm shared
-- @string directory The path to the factions files.
function ix.faction.LoadFromDir(directory)
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
local niceName = v:sub(4, -5)
FACTION = ix.faction.teams[niceName] or {index = table.Count(ix.faction.teams) + 1, isDefault = false}
if (PLUGIN) then
FACTION.plugin = PLUGIN.uniqueID
end
ix.util.Include(directory.."/"..v, "shared")
if (!FACTION.name) then
FACTION.name = "Unknown"
ErrorNoHalt("Faction '"..niceName.."' is missing a name. You need to add a FACTION.name = \"Name\"\n")
end
if (!FACTION.color) then
FACTION.color = Color(150, 150, 150)
ErrorNoHalt("Faction '"..niceName.."' is missing a color. You need to add FACTION.color = Color(1, 2, 3)\n")
end
team.SetUp(FACTION.index, FACTION.name or "Unknown", FACTION.color or Color(125, 125, 125))
FACTION.models = FACTION.models or CITIZEN_MODELS
FACTION.uniqueID = FACTION.uniqueID or niceName
for _, v2 in pairs(FACTION.models) do
if (isstring(v2)) then
util.PrecacheModel(v2)
elseif (istable(v2)) then
util.PrecacheModel(v2[1])
end
end
if (!FACTION.GetModels) then
function FACTION:GetModels(client)
return self.models
end
end
ix.faction.indices[FACTION.index] = FACTION
ix.faction.teams[niceName] = FACTION
FACTION = nil
end
end
--- Retrieves a faction table.
-- @realm shared
-- @param identifier Index or name of the faction
-- @treturn table Faction table
-- @usage print(ix.faction.Get(Entity(1):Team()).name)
-- > "Citizen"
function ix.faction.Get(identifier)
return ix.faction.indices[identifier] or ix.faction.teams[identifier]
end
--- Retrieves a faction index.
-- @realm shared
-- @string uniqueID Unique ID of the faction
-- @treturn number Faction index
function ix.faction.GetIndex(uniqueID)
for k, v in ipairs(ix.faction.indices) do
if (v.uniqueID == uniqueID) then
return k
end
end
end
if (CLIENT) then
--- Returns true if a faction requires a whitelist.
-- @realm client
-- @number faction Index of the faction
-- @treturn bool Whether or not the faction requires a whitelist
function ix.faction.HasWhitelist(faction)
local data = ix.faction.indices[faction]
if (data) then
if (data.isDefault) then
return true
end
local ixData = ix.localData and ix.localData.whitelists or {}
return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false
end
return false
end
end
================================================
FILE: gamemode/core/libs/sh_flag.lua
================================================
--[[--
Grants abilities to characters.
Flags are a simple way of adding/removing certain abilities to players on a per-character basis. Helix comes with a few flags
by default, for example to restrict spawning of props, usage of the physgun, etc. All flags will be listed in the
`Flags` section of the `Help` menu. Flags are usually used when server validation is required to allow a player to do something
on their character. However, it's usually preferable to use in-character methods over flags when possible (i.e restricting
the business menu to characters that have a permit item, rather than using flags to determine availability).
Flags are a single alphanumeric character that can be checked on the server. Serverside callbacks can be used to provide
functionality whenever the flag is added or removed. For example:
ix.flag.Add("z", "Access to some cool stuff.", function(client, bGiven)
print("z flag given:", bGiven)
end)
Entity(1):GetCharacter():GiveFlags("z")
> z flag given: true
Entity(1):GetCharacter():TakeFlags("z")
> z flag given: false
print(Entity(1):GetCharacter():HasFlags("z"))
> false
Check out `Character:GiveFlags` and `Character:TakeFlags` for additional info.
]]
-- @module ix.flag
ix.flag = ix.flag or {}
ix.flag.list = ix.flag.list or {}
--- Creates a flag. This should be called shared in order for the client to be aware of the flag's existence.
-- @realm shared
-- @string flag Alphanumeric character to use for the flag
-- @string description Description of the flag
-- @func callback Function to call when the flag is given or taken from a player
function ix.flag.Add(flag, description, callback)
ix.flag.list[flag] = {
description = description,
callback = callback
}
end
if (SERVER) then
-- Called to apply flags when a player has spawned.
-- @realm server
-- @internal
-- @player client Player to setup flags for
function ix.flag.OnSpawn(client)
-- Check if they have a valid character.
if (client:GetCharacter()) then
-- Get all of the character's flags.
local flags = client:GetCharacter():GetFlags()
for i = 1, #flags do
-- Get each individual flag.
local flag = flags[i]
local info = ix.flag.list[flag]
-- Check if the flag has a callback.
if (info and info.callback) then
-- Run the callback, passing the player and true so they get whatever benefits.
info.callback(client, true)
end
end
end
end
end
do
local character = ix.meta.character
if (SERVER) then
--- Flag util functions for character
-- @classmod Character
--- Sets this character's accessible flags. Note that this method overwrites **all** flags instead of adding them.
-- @realm server
-- @string flags Flag(s) this charater is allowed to have
-- @see GiveFlags
function character:SetFlags(flags)
self:SetData("f", flags)
end
--- Adds a flag to the list of this character's accessible flags. This does not overwrite existing flags.
-- @realm server
-- @string flags Flag(s) this character should be given
-- @usage character:GiveFlags("pet")
-- -- gives p, e, and t flags to the character
-- @see HasFlags
function character:GiveFlags(flags)
local addedFlags = ""
-- Get the individual flags within the flag string.
for i = 1, #flags do
local flag = flags[i]
local info = ix.flag.list[flag]
if (info) then
if (!self:HasFlags(flag)) then
addedFlags = addedFlags..flag
end
if (info.callback) then
-- Pass the player and true (true for the flag being given.)
info.callback(self:GetPlayer(), true)
end
end
end
-- Only change the flag string if it is different.
if (addedFlags != "") then
self:SetFlags(self:GetFlags()..addedFlags)
end
end
--- Removes this character's access to the given flags.
-- @realm server
-- @string flags Flag(s) to remove from this character
-- @usage -- for a character with "pet" flags
-- character:TakeFlags("p")
-- -- character now has e, and t flags
function character:TakeFlags(flags)
local oldFlags = self:GetFlags()
local newFlags = oldFlags
-- Get the individual flags within the flag string.
for i = 1, #flags do
local flag = flags[i]
local info = ix.flag.list[flag]
-- Call the callback if the flag has been registered.
if (info and info.callback) then
-- Pass the player and false (false since the flag is being taken)
info.callback(self:GetPlayer(), false)
end
newFlags = newFlags:gsub(flag, "")
end
if (newFlags != oldFlags) then
self:SetFlags(newFlags)
end
end
end
--- Returns all of the flags this character has.
-- @realm shared
-- @treturn string Flags this character has represented as one string. You can access individual flags by iterating through
-- the string letter by letter
function character:GetFlags()
return self:GetData("f", "")
end
--- Returns `true` if the character has the given flag(s).
-- @realm shared
-- @string flags Flag(s) to check access for
-- @treturn bool Whether or not this character has access to the given flag(s)
function character:HasFlags(flags)
local bHasFlag = hook.Run("CharacterHasFlags", self, flags)
if (bHasFlag == true) then
return true
end
local flagList = self:GetFlags()
for i = 1, #flags do
if (flagList:find(flags[i], 1, true)) then
return true
end
end
return false
end
end
do
ix.flag.Add("p", "Access to the physgun.", function(client, isGiven)
if (isGiven) then
client:Give("weapon_physgun")
client:SelectWeapon("weapon_physgun")
else
client:StripWeapon("weapon_physgun")
end
end)
ix.flag.Add("t", "Access to the toolgun", function(client, isGiven)
if (isGiven) then
client:Give("gmod_tool")
client:SelectWeapon("gmod_tool")
else
client:StripWeapon("gmod_tool")
end
end)
ix.flag.Add("c", "Access to spawn chairs.")
ix.flag.Add("C", "Access to spawn vehicles.")
ix.flag.Add("r", "Access to spawn ragdolls.")
ix.flag.Add("e", "Access to spawn props.")
ix.flag.Add("n", "Access to spawn NPCs.")
end
================================================
FILE: gamemode/core/libs/sh_inventory.lua
================================================
--[[--
Inventory manipulation and helper functions.
]]
-- @module ix.inventory
ix.inventory = ix.inventory or {}
ix.util.Include("helix/gamemode/core/meta/sh_inventory.lua")
--- Retrieves an inventory table.
-- @realm shared
-- @number invID Index of the inventory
-- @treturn Inventory Inventory table
function ix.inventory.Get(invID)
return ix.item.inventories[invID]
end
function ix.inventory.Create(width, height, id)
local inventory = ix.meta.inventory:New(id, width, height)
ix.item.inventories[id] = inventory
return inventory
end
--- Loads an inventory and associated items from the database into memory. If you are passing a table into `invID`, it
-- requires a table where the key is the inventory ID, and the value is a table of the width and height values. See below
-- for an example.
-- @realm server
-- @param invID Inventory ID or table of inventory IDs
-- @number width Width of inventory (this is not used when passing a table to `invID`)
-- @number height Height of inventory (this is not used when passing a table to `invID`)
-- @func callback Function to call when inventory is restored
-- @usage ix.inventory.Restore({
-- [10] = {5, 5},
-- [11] = {7, 4}
-- })
-- -- inventories 10 and 11 with sizes (5, 5) and (7, 4) will be loaded
function ix.inventory.Restore(invID, width, height, callback)
local inventories = {}
if (!istable(invID)) then
if (!isnumber(invID) or invID < 0) then
error("Attempt to restore inventory with an invalid ID!")
end
inventories[invID] = {width, height}
ix.inventory.Create(width, height, invID)
else
for k, v in pairs(invID) do
inventories[k] = {v[1], v[2]}
ix.inventory.Create(v[1], v[2], k)
end
end
local query = mysql:Select("ix_items")
query:Select("item_id")
query:Select("inventory_id")
query:Select("unique_id")
query:Select("data")
query:Select("character_id")
query:Select("player_id")
query:Select("x")
query:Select("y")
query:WhereIn("inventory_id", table.GetKeys(inventories))
query:Callback(function(result)
if (istable(result) and #result > 0) then
local invSlots = {}
for _, item in ipairs(result) do
local itemInvID = tonumber(item.inventory_id)
local invInfo = inventories[itemInvID]
if (!itemInvID or !invInfo) then
-- don't restore items with an invalid inventory id or type
continue
end
local inventory = ix.item.inventories[itemInvID]
local x, y = tonumber(item.x), tonumber(item.y)
local itemID = tonumber(item.item_id)
local data = util.JSONToTable(item.data or "[]")
local characterID, playerID = tonumber(item.character_id), tostring(item.player_id)
if (x and y and itemID) then
if (x <= inventory.w and x > 0 and y <= inventory.h and y > 0) then
local item2 = ix.item.New(item.unique_id, itemID)
if (item2) then
invSlots[itemInvID] = invSlots[itemInvID] or {}
local slots = invSlots[itemInvID]
item2.data = {}
if (data) then
item2.data = data
end
item2.gridX = x
item2.gridY = y
item2.invID = itemInvID
item2.characterID = characterID
item2.playerID = (playerID == "" or playerID == "NULL") and nil or playerID
for x2 = 0, item2.width - 1 do
for y2 = 0, item2.height - 1 do
slots[x + x2] = slots[x + x2] or {}
slots[x + x2][y + y2] = item2
end
end
if (item2.OnRestored) then
item2:OnRestored(item2, itemInvID)
end
end
end
end
end
for k, v in pairs(invSlots) do
ix.item.inventories[k].slots = v
end
end
if (callback) then
for k, _ in pairs(inventories) do
callback(ix.item.inventories[k])
end
end
end)
query:Execute()
end
function ix.inventory.New(owner, invType, callback)
local invData = ix.item.inventoryTypes[invType] or {w = 1, h = 1}
local query = mysql:Insert("ix_inventories")
query:Insert("inventory_type", invType)
query:Insert("character_id", owner)
query:Callback(function(result, status, lastID)
local inventory = ix.inventory.Create(invData.w, invData.h, lastID)
if (invType) then
inventory.vars.isBag = invType
end
if (isnumber(owner) and owner > 0) then
local character = ix.char.loaded[owner]
local client = character:GetPlayer()
inventory:SetOwner(owner)
if (IsValid(client)) then
inventory:Sync(client)
end
end
if (callback) then
callback(inventory)
end
end)
query:Execute()
end
function ix.inventory.Register(invType, w, h, isBag)
ix.item.inventoryTypes[invType] = {w = w, h = h}
if (isBag) then
ix.item.inventoryTypes[invType].isBag = invType
end
return ix.item.inventoryTypes[invType]
end
================================================
FILE: gamemode/core/libs/sh_item.lua
================================================
--[[--
Item manipulation and helper functions.
]]
-- @module ix.item
ix.item = ix.item or {}
ix.item.list = ix.item.list or {}
ix.item.base = ix.item.base or {}
ix.item.instances = ix.item.instances or {}
ix.item.inventories = ix.item.inventories or {
[0] = {}
}
ix.item.inventoryTypes = ix.item.inventoryTypes or {}
ix.util.Include("helix/gamemode/core/meta/sh_item.lua")
-- Declare some supports for logic inventory
local zeroInv = ix.item.inventories[0]
function zeroInv:GetID()
return 0
end
function zeroInv:OnCheckAccess(client)
return true
end
-- WARNING: You have to manually sync the data to client if you're trying to use item in the logical inventory in the vgui.
function zeroInv:Add(uniqueID, quantity, data, x, y)
quantity = quantity or 1
if (quantity > 0) then
if (!isnumber(uniqueID)) then
if (quantity > 1) then
for _ = 1, quantity do
self:Add(uniqueID, 1, data)
end
return
end
local itemTable = ix.item.list[uniqueID]
if (!itemTable) then
return false, "invalidItem"
end
ix.item.Instance(0, uniqueID, data, x, y, function(item)
self[item:GetID()] = item
end)
return nil, nil, 0
end
else
return false, "notValid"
end
end
function ix.item.Instance(index, uniqueID, itemData, x, y, callback, characterID, playerID)
if (!uniqueID or ix.item.list[uniqueID]) then
itemData = istable(itemData) and itemData or {}
local query = mysql:Insert("ix_items")
query:Insert("inventory_id", index)
query:Insert("unique_id", uniqueID)
query:Insert("data", util.TableToJSON(itemData))
query:Insert("x", x)
query:Insert("y", y)
if (characterID) then
query:Insert("character_id", characterID)
end
if (playerID) then
query:Insert("player_id", playerID)
end
query:Callback(function(result, status, lastID)
local item = ix.item.New(uniqueID, lastID)
if (item) then
item.data = table.Copy(itemData)
item.invID = index
item.characterID = characterID
item.playerID = playerID
if (callback) then
callback(item)
end
if (item.OnInstanced) then
item:OnInstanced(index, x, y, item)
end
end
end)
query:Execute()
else
ErrorNoHalt("[Helix] Attempt to give an invalid item! (" .. (uniqueID or "nil") .. ")\n")
end
end
--- Retrieves an item table.
-- @realm shared
-- @string identifier Unique ID of the item
-- @treturn item Item table
-- @usage print(ix.item.Get("example"))
-- > "item[example][0]"
function ix.item.Get(identifier)
return ix.item.base[identifier] or ix.item.list[identifier]
end
function ix.item.Load(path, baseID, isBaseItem)
local uniqueID = path:match("sh_([_%w]+)%.lua")
if (uniqueID) then
uniqueID = (isBaseItem and "base_" or "")..uniqueID
ix.item.Register(uniqueID, baseID, isBaseItem, path)
else
if (!path:find(".txt")) then
ErrorNoHalt("[Helix] Item at '"..path.."' follows invalid naming convention!\n")
end
end
end
function ix.item.Register(uniqueID, baseID, isBaseItem, path, luaGenerated)
local meta = ix.meta.item
if (uniqueID) then
ITEM = (isBaseItem and ix.item.base or ix.item.list)[uniqueID] or setmetatable({}, meta)
ITEM.uniqueID = uniqueID
ITEM.base = baseID or ITEM.base
ITEM.isBase = isBaseItem or false
ITEM.hooks = ITEM.hooks or {}
ITEM.postHooks = ITEM.postHooks or {}
ITEM.functions = ITEM.functions or {}
ITEM.functions.drop = ITEM.functions.drop or {
tip = "dropTip",
icon = "icon16/world.png",
OnRun = function(item)
local bSuccess, error = item:Transfer(nil, nil, nil, item.player)
if (!bSuccess and isstring(error)) then
item.player:NotifyLocalized(error)
else
item.player:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1)
end
return false
end,
OnCanRun = function(item)
return !IsValid(item.entity) and !item.noDrop
end
}
ITEM.functions.take = ITEM.functions.take or {
tip = "takeTip",
icon = "icon16/box.png",
OnRun = function(item)
local client = item.player
local bSuccess, error = item:Transfer(client:GetCharacter():GetInventory():GetID(), nil, nil, client)
if (!bSuccess) then
client:NotifyLocalized(error or "unknownError")
return false
else
client:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1)
if (item.data) then -- I don't like it but, meh...
for k, v in pairs(item.data) do
item:SetData(k, v)
end
end
end
return true
end,
OnCanRun = function(item)
return IsValid(item.entity)
end
}
local oldBase = ITEM.base
if (ITEM.base) then
local baseTable = ix.item.base[ITEM.base]
if (baseTable) then
for k, v in pairs(baseTable) do
if (ITEM[k] == nil) then
ITEM[k] = v
end
ITEM.baseTable = baseTable
end
local mergeTable = table.Copy(baseTable)
ITEM = table.Merge(mergeTable, ITEM)
else
ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n")
end
end
if (PLUGIN) then
ITEM.plugin = PLUGIN.uniqueID
end
if (!luaGenerated and path) then
ix.util.Include(path)
end
if (ITEM.base and oldBase != ITEM.base) then
local baseTable = ix.item.base[ITEM.base]
if (baseTable) then
for k, v in pairs(baseTable) do
if (ITEM[k] == nil) then
ITEM[k] = v
end
ITEM.baseTable = baseTable
end
local mergeTable = table.Copy(baseTable)
ITEM = table.Merge(mergeTable, ITEM)
else
ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n")
end
end
ITEM.description = ITEM.description or "noDesc"
ITEM.width = ITEM.width or 1
ITEM.height = ITEM.height or 1
ITEM.category = ITEM.category or "misc"
if (ITEM.OnRegistered) then
ITEM:OnRegistered()
end
(isBaseItem and ix.item.base or ix.item.list)[ITEM.uniqueID] = ITEM
if (IX_RELOADED) then
-- we don't know which item was actually edited, so we'll refresh all of them
for _, v in pairs(ix.item.instances) do
if (v.uniqueID == uniqueID) then
ix.util.MetatableSafeTableMerge(v, ITEM)
end
end
end
if (luaGenerated) then
return ITEM
else
ITEM = nil
end
else
ErrorNoHalt("[Helix] You tried to register an item without uniqueID!\n")
end
end
function ix.item.LoadFromDir(directory)
local files, folders
files = file.Find(directory.."/base/*.lua", "LUA")
for _, v in ipairs(files) do
ix.item.Load(directory.."/base/"..v, nil, true)
end
files, folders = file.Find(directory.."/*", "LUA")
for _, v in ipairs(folders) do
if (v == "base") then
continue
end
for _, v2 in ipairs(file.Find(directory.."/"..v.."/*.lua", "LUA")) do
ix.item.Load(directory.."/"..v .. "/".. v2, "base_"..v)
end
end
for _, v in ipairs(files) do
ix.item.Load(directory.."/"..v)
end
end
function ix.item.New(uniqueID, id)
if (ix.item.instances[id] and ix.item.instances[id].uniqueID == uniqueID) then
return ix.item.instances[id]
end
local stockItem = ix.item.list[uniqueID]
if (stockItem) then
local item = setmetatable({id = id, data = {}}, {
__index = stockItem,
__eq = stockItem.__eq,
__tostring = stockItem.__tostring
})
ix.item.instances[id] = item
return item
else
ErrorNoHalt("[Helix] Attempt to index unknown item '"..uniqueID.."'\n")
end
end
do
function ix.item.GetInv(invID)
ErrorNoHalt("ix.item.GetInv is deprecated. Use ix.inventory.Get instead!\n")
return ix.inventory.Get(invID)
end
function ix.item.RegisterInv(invType, w, h, isBag)
ErrorNoHalt("ix.item.RegisterInv is deprecated. Use ix.inventory.Register instead!\n")
return ix.inventory.Register(invType, w, h, isBag)
end
function ix.item.NewInv(owner, invType, callback)
ErrorNoHalt("ix.item.NewInv is deprecated. Use ix.inventory.New instead!\n")
return ix.inventory.New(owner, invType, callback)
end
function ix.item.CreateInv(width, height, id)
ErrorNoHalt("ix.item.CreateInv is deprecated. Use ix.inventory.Create instead!\n")
return ix.inventory.Create(width, height, id)
end
function ix.item.RestoreInv(invID, width, height, callback)
ErrorNoHalt("ix.item.RestoreInv is deprecated. Use ix.inventory.Restore instead!\n")
return ix.inventory.Restore(invID, width, height, callback)
end
if (CLIENT) then
net.Receive("ixInventorySync", function()
local slots = net.ReadTable()
local id = net.ReadUInt(32)
local w, h = net.ReadUInt(6), net.ReadUInt(6)
local owner = net.ReadType()
local vars = net.ReadTable()
if (!LocalPlayer():GetCharacter()) then
return
end
local character = owner and ix.char.loaded[owner]
local inventory = ix.inventory.Create(w, h, id)
inventory.slots = {}
inventory.vars = vars
local x, y
for _, v in ipairs(slots) do
x, y = v[1], v[2]
inventory.slots[x] = inventory.slots[x] or {}
local item = ix.item.New(v[3], v[4])
item.data = {}
if (v[5]) then
item.data = v[5]
end
item.invID = item.invID or id
inventory.slots[x][y] = item
end
if (character) then
inventory:SetOwner(character:GetID())
character.vars.inv = character.vars.inv or {}
for k, v in ipairs(character:GetInventory(true)) do
if (v:GetID() == id) then
character:GetInventory(true)[k] = inventory
return
end
end
table.insert(character.vars.inv, inventory)
end
end)
net.Receive("ixInventoryData", function()
local id = net.ReadUInt(32)
local item = ix.item.instances[id]
if (item) then
local key = net.ReadString()
local value = net.ReadType()
item.data = item.data or {}
item.data[key] = value
local invID = item.invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or item.invID
local panel = ix.gui["inv" .. invID]
if (panel and panel.panels) then
local icon = panel.panels[id]
if (icon) then
icon:SetHelixTooltip(function(tooltip)
ix.hud.PopulateItemTooltip(tooltip, item)
end)
end
end
end
end)
net.Receive("ixInventorySet", function()
local invID = net.ReadUInt(32)
local x, y = net.ReadUInt(6), net.ReadUInt(6)
local uniqueID = net.ReadString()
local id = net.ReadUInt(32)
local owner = net.ReadUInt(32)
local data = net.ReadTable()
local character = owner != 0 and ix.char.loaded[owner] or LocalPlayer():GetCharacter()
if (character) then
local inventory = ix.item.inventories[invID]
if (inventory) then
local item = (uniqueID != "" and id != 0) and ix.item.New(uniqueID, id) or nil
item.invID = invID
item.data = {}
if (data) then
item.data = data
end
inventory.slots[x] = inventory.slots[x] or {}
inventory.slots[x][y] = item
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
local panel = ix.gui["inv" .. invID]
if (IsValid(panel)) then
local icon = panel:AddIcon(
item:GetModel() or "models/props_junk/popcan01a.mdl", x, y, item.width, item.height, item:GetSkin()
)
if (IsValid(icon)) then
icon:SetHelixTooltip(function(tooltip)
ix.hud.PopulateItemTooltip(tooltip, item)
end)
icon.itemID = item.id
panel.panels[item.id] = icon
end
end
end
end
end)
net.Receive("ixInventoryMove", function()
local invID = net.ReadUInt(32)
local inventory = ix.item.inventories[invID]
if (!inventory) then
return
end
local itemID = net.ReadUInt(32)
local oldX = net.ReadUInt(6)
local oldY = net.ReadUInt(6)
local x = net.ReadUInt(6)
local y = net.ReadUInt(6)
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
local item = ix.item.instances[itemID]
local panel = ix.gui["inv" .. invID]
-- update inventory UI if it's open
if (IsValid(panel)) then
local icon = panel.panels[itemID]
if (IsValid(icon)) then
icon:Move(x, y, panel, true)
end
end
-- update inventory slots
if (item) then
inventory.slots[oldX][oldY] = nil
inventory.slots[x] = inventory.slots[x] or {}
inventory.slots[x][y] = item
end
end)
net.Receive("ixInventoryRemove", function()
local id = net.ReadUInt(32)
local invID = net.ReadUInt(32)
local inventory = ix.item.inventories[invID]
if (!inventory) then
return
end
inventory:Remove(id)
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
local panel = ix.gui["inv" .. invID]
if (IsValid(panel)) then
local icon = panel.panels[id]
if (IsValid(icon)) then
for _, v in ipairs(icon.slots or {}) do
if (v.item == icon) then
v.item = nil
end
end
icon:Remove()
end
end
local item = ix.item.instances[id]
if (!item) then
return
end
-- we need to close any bag windows that are open because of this item
if (item.isBag) then
local itemInv = item:GetInventory()
if (itemInv) then
local frame = ix.gui["inv" .. itemInv:GetID()]
if (IsValid(frame)) then
frame:Remove()
end
end
end
end)
else
util.AddNetworkString("ixInventorySync")
util.AddNetworkString("ixInventorySet")
util.AddNetworkString("ixInventoryMove")
util.AddNetworkString("ixInventoryRemove")
util.AddNetworkString("ixInventoryData")
util.AddNetworkString("ixInventoryAction")
function ix.item.LoadItemByID(itemIndex, recipientFilter)
local query = mysql:Select("ix_items")
query:Select("item_id")
query:Select("unique_id")
query:Select("data")
query:Select("character_id")
query:Select("player_id")
query:WhereIn("item_id", itemIndex)
query:Callback(function(result)
if (istable(result)) then
for _, v in ipairs(result) do
local itemID = tonumber(v.item_id)
local data = util.JSONToTable(v.data or "[]")
local uniqueID = v.unique_id
local itemTable = ix.item.list[uniqueID]
local characterID = tonumber(v.character_id)
local playerID = tostring(v.player_id)
if (itemTable and itemID) then
local item = ix.item.New(uniqueID, itemID)
item.data = data or {}
item.invID = 0
item.characterID = characterID
item.playerID = (playerID == "" or playerID == "NULL") and nil or playerID
end
end
end
end)
query:Execute()
end
function ix.item.PerformInventoryAction(client, action, item, invID, data)
local character = client:GetCharacter()
if (!character) then
return
end
local inventory = ix.item.inventories[invID or 0]
if (hook.Run("CanPlayerInteractItem", client, action, item, data) == false) then
return
end
if (!inventory:OnCheckAccess(client)) then
return
end
if (isentity(item)) then
if (IsValid(item)) then
local entity = item
local itemID = item.ixItemID
item = ix.item.instances[itemID]
if (!item) then
return
end
item.entity = entity
item.player = client
else
return
end
elseif (isnumber(item)) then
item = ix.item.instances[item]
if (!item) then
return
end
item.player = client
end
if (item.entity) then
if (client:GetShootPos():DistToSqr(item.entity:GetPos()) > 96 * 96) then
return
end
local useEntity = client:GetUseEntity()
if (IsValid(useEntity) and item.entity != useEntity) then
return
end
elseif (!inventory:GetItemByID(item.id)) then
return
end
if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then
local itemPlayerID = item:GetPlayerID()
local itemCharacterID = item:GetCharacterID()
local playerID = client:SteamID64()
local characterID = client:GetCharacter():GetID()
if (itemPlayerID and itemCharacterID and itemPlayerID == playerID and itemCharacterID != characterID) then
client:NotifyLocalized("itemOwned")
item.player = nil
item.entity = nil
return
end
end
local callback = item.functions[action]
if (callback) then
if (callback.OnCanRun and callback.OnCanRun(item, data) == false) then
item.entity = nil
item.player = nil
return
end
hook.Run("PlayerInteractItem", client, action, item)
local entity = item.entity
local result
if (item.hooks[action]) then
result = item.hooks[action](item, data)
end
if (result == nil) then
result = callback.OnRun(item, data)
end
if (item.postHooks[action]) then
-- Posthooks shouldn't override the result from OnRun
item.postHooks[action](item, result, data)
end
if (result != false) then
if (IsValid(entity)) then
entity.ixIsSafe = true
entity:Remove()
else
item:Remove()
end
end
item.entity = nil
item.player = nil
return result != false
end
end
local function NetworkInventoryMove(receiver, invID, itemID, oldX, oldY, x, y)
net.Start("ixInventoryMove")
net.WriteUInt(invID, 32)
net.WriteUInt(itemID, 32)
net.WriteUInt(oldX, 6)
net.WriteUInt(oldY, 6)
net.WriteUInt(x, 6)
net.WriteUInt(y, 6)
net.Send(receiver)
end
net.Receive("ixInventoryMove", function(length, client)
local oldX, oldY, x, y = net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6)
local invID, newInvID = net.ReadUInt(32), net.ReadUInt(32)
local character = client:GetCharacter()
if (character) then
local inventory = ix.item.inventories[invID]
if (!inventory) then
return
end
if ((inventory.owner and inventory.owner == character:GetID()) or inventory:OnCheckAccess(client)) then
local item = inventory:GetItemAt(oldX, oldY)
if (item) then
if (newInvID and invID != newInvID) then
local inventory2 = ix.item.inventories[newInvID]
if (inventory2) then
local bStatus, error = item:Transfer(newInvID, x, y, client)
if (!bStatus) then
NetworkInventoryMove(
client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
)
client:NotifyLocalized(error or "unknownError")
end
end
return
end
if (inventory:CanItemFit(x, y, item.width, item.height, item)) then
item.gridX = x
item.gridY = y
for x2 = 0, item.width - 1 do
for y2 = 0, item.height - 1 do
local previousX = inventory.slots[oldX + x2]
if (previousX) then
previousX[oldY + y2] = nil
end
end
end
for x2 = 0, item.width - 1 do
for y2 = 0, item.height - 1 do
inventory.slots[x + x2] = inventory.slots[x + x2] or {}
inventory.slots[x + x2][y + y2] = item
end
end
local receivers = inventory:GetReceivers()
if (istable(receivers)) then
local filtered = {}
for _, v in ipairs(receivers) do
if (v != client) then
filtered[#filtered + 1] = v
end
end
if (#filtered > 0) then
NetworkInventoryMove(
filtered, invID, item:GetID(), oldX, oldY, x, y
)
end
end
if (!inventory.noSave) then
local query = mysql:Update("ix_items")
query:Update("x", x)
query:Update("y", y)
query:Where("item_id", item.id)
query:Execute()
end
else
NetworkInventoryMove(
client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
)
end
end
else
local item = inventory:GetItemAt(oldX, oldY)
if (item) then
NetworkInventoryMove(
client, item.invID, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
)
end
end
end
end)
net.Receive("ixInventoryAction", function(length, client)
ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadUInt(32), net.ReadUInt(32), net.ReadTable())
end)
end
--- Instances and spawns a given item type.
-- @realm server
-- @string uniqueID Unique ID of the item
-- @vector position The position in which the item's entity will be spawned
-- @func[opt=nil] callback Function to call when the item entity is created
-- @angle[opt=angle_zero] angles The angles at which the item's entity will spawn
-- @tab[opt=nil] data Additional data for this item instance
function ix.item.Spawn(uniqueID, position, callback, angles, data)
ix.item.Instance(0, uniqueID, data or {}, 1, 1, function(item)
local entity = item:Spawn(position, angles)
if (callback) then
callback(item, entity)
end
end)
end
end
--- Inventory util functions for character
-- @classmod Character
--- Returns this character's associated `Inventory` object.
-- @function GetInventory
-- @realm shared
-- @treturn Inventory This character's inventory
ix.char.RegisterVar("Inventory", {
bNoNetworking = true,
bNoDisplay = true,
OnGet = function(character, index)
if (index and !isnumber(index)) then
return character.vars.inv or {}
end
return character.vars.inv and character.vars.inv[index or 1]
end,
alias = "Inv"
})
================================================
FILE: gamemode/core/libs/sh_language.lua
================================================
--[[--
Multi-language phrase support.
Helix has support for multiple languages, and you can easily leverage this system for use in your own schema, plugins, etc.
Languages will be loaded from the schema and any plugins in `languages/sh_languagename.lua`, where `languagename` is the id of a
language (`english` for English, `french` for French, etc). The structure of a language file is a table of phrases with the key
as its phrase ID and the value as its translation for that language. For example, in `plugins/area/languages/sh_english.lua`:
LANGUAGE = {
area = "Area",
areas = "Areas",
areaEditMode = "Area Edit Mode",
-- etc.
}
The phrases defined in these language files can be used with the `L` global function:
print(L("areaEditMode"))
> Area Edit Mode
All phrases are formatted with `string.format`, so if you wish to add some info in a phrase you can use standard Lua string
formatting arguments:
print(L("areaDeleteConfirm", "Test"))
> Are you sure you want to delete the area "Test"?
Phrases are also usable on the server, but only when trying to localize a phrase based on a client's preferences. The server
does not have a set language. An example:
Entity(1):ChatPrint(L("areaEditMode"))
> -- "Area Edit Mode" will print in the player's chatbox
]]
-- @module ix.lang
ix.lang = ix.lang or {}
ix.lang.stored = ix.lang.stored or {}
ix.lang.names = ix.lang.names or {}
--- Loads language files from a directory.
-- @realm shared
-- @internal
-- @string directory Directory to load language files from
function ix.lang.LoadFromDir(directory)
for _, v in ipairs(file.Find(directory.."/sh_*.lua", "LUA")) do
local niceName = v:sub(4, -5):lower()
ix.util.Include(directory.."/"..v, "shared")
if (LANGUAGE) then
if (NAME) then
ix.lang.names[niceName] = NAME
NAME = nil
end
ix.lang.AddTable(niceName, LANGUAGE)
LANGUAGE = nil
end
end
end
--- Adds phrases to a language. This is used when you aren't adding entries through the files in the `languages/` folder. A
-- common use case is adding language phrases in a single-file plugin.
-- @realm shared
-- @string language The ID of the language
-- @tab data Language data to add to the given language
-- @usage ix.lang.AddTable("english", {
-- myPhrase = "My Phrase"
-- })
function ix.lang.AddTable(language, data)
language = tostring(language):lower()
ix.lang.stored[language] = table.Merge(ix.lang.stored[language] or {}, data)
end
if (SERVER) then
-- luacheck: globals L
function L(key, client, ...)
local languages = ix.lang.stored
local langKey = ix.option.Get(client, "language", "english")
local info = languages[langKey] or languages.english
return string.format(info and info[key] or languages.english[key] or key, ...)
end
-- luacheck: globals L2
function L2(key, client, ...)
local languages = ix.lang.stored
local langKey = ix.option.Get(client, "language", "english")
local info = languages[langKey] or languages.english
if (info and info[key]) then
return string.format(info[key], ...)
end
end
else
function L(key, ...)
local languages = ix.lang.stored
local langKey = ix.option.Get("language", "english")
local info = languages[langKey] or languages.english
return string.format(info and info[key] or languages.english[key] or key, ...)
end
function L2(key, ...)
local langKey = ix.option.Get("language", "english")
local info = ix.lang.stored[langKey]
if (info and info[key]) then
return string.format(info[key], ...)
end
end
end
================================================
FILE: gamemode/core/libs/sh_log.lua
================================================
--[[--
Logging helper functions.
Predefined flags:
FLAG_NORMAL
FLAG_SUCCESS
FLAG_WARNING
FLAG_DANGER
FLAG_SERVER
FLAG_DEV
]]
-- @module ix.log
-- luacheck: globals FLAG_NORMAL FLAG_SUCCESS FLAG_WARNING FLAG_DANGER FLAG_SERVER FLAG_DEV
FLAG_NORMAL = 0
FLAG_SUCCESS = 1
FLAG_WARNING = 2
FLAG_DANGER = 3
FLAG_SERVER = 4
FLAG_DEV = 5
ix.log = ix.log or {}
ix.log.color = {
[FLAG_NORMAL] = Color(200, 200, 200),
[FLAG_SUCCESS] = Color(50, 200, 50),
[FLAG_WARNING] = Color(255, 255, 0),
[FLAG_DANGER] = Color(255, 50, 50),
[FLAG_SERVER] = Color(200, 200, 220),
[FLAG_DEV] = Color(200, 200, 220),
}
CAMI.RegisterPrivilege({
Name = "Helix - Logs",
MinAccess = "admin"
})
local consoleColor = Color(50, 200, 50)
if (SERVER) then
if (!ix.db) then
include("sv_database.lua")
end
util.AddNetworkString("ixLogStream")
function ix.log.LoadTables()
ix.log.CallHandler("Load")
end
ix.log.types = ix.log.types or {}
--- Registers a log type for `ix.log.Add`.
-- `format` is either a format string or a function `(client, ...) -> string`.
--
-- `flag` is a display/severity tag (see `FLAG_*`). If omitted, it behaves like a normal log.
-- @realm server
-- @tparam string logType Type name used when calling `ix.log.Add`.
-- @tparam string|function format Format string or formatter function.
-- @tparam[opt] number flag One of the `FLAG_*` constants.
-- @usage
-- ix.log.AddType("charMoneyGive", "%s gave %s %d tokens.", FLAG_SUCCESS)
function ix.log.AddType(logType, format, flag)
ix.log.types[logType] = {format = format, flag = flag}
end
--- Turns a log type + args into `(message, flag)`.
-- Missing type falls back to a warning string. No formatter returns `-1` (caller bails).
-- @internal
-- @realm server
-- @tparam Player|nil client Player tied to the log.
-- @tparam string logType Log type name.
-- @param ... Arguments for the formatter.
-- @treturn string|number Message string, or -1 to skip logging.
-- @treturn number Flag from the registered type.
function ix.log.Parse(client, logType, ...)
local info = ix.log.types[logType]
if (!info) then
ErrorNoHalt("attempted to add entry to non-existent log type \"" .. tostring(logType) .. "\"\n")
local fallback = logType.." : "
if (client) then
fallback = fallback..client:Name().." - "
end
for _, v in ipairs({...}) do
fallback = fallback..tostring(v).." "
end
return fallback, FLAG_WARNING
end
local text = info and info.format
if (text) then
if (isfunction(text)) then
text = text(client, ...)
end
else
text = -1
end
return text, info.flag
end
--- Adds a log entry without using a log type.
-- Sends the message to admins with log access, prints it to the server console,
-- and writes it through log handlers unless `bNoSave` is true.
-- @realm server
-- @tparam string logString Pre-formatted log message.
-- @tparam[opt] boolean bNoSave If true, skips handler output (e.g. file logging).
-- @usage ix.log.AddRaw("Server is restarting in 5 minutes.")
-- @see ix.log.Add
function ix.log.AddRaw(logString, bNoSave)
CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers)
ix.log.Send(receivers, logString)
end)
Msg("[LOG] ", logString .. "\n")
if (!bNoSave) then
ix.log.CallHandler("Write", nil, logString)
end
end
--- Adds a typed log entry.
-- The entry is visible to admins with log access, printed to the server console,
-- and written through the logging system.
-- @realm server
-- @tparam Player|nil client Player tied to the log (can be nil).
-- @tparam string logType Type registered with `ix.log.AddType`.
-- @param ... Arguments for the formatter.
-- @usage ix.log.Add(client, "charMoneyGive", giverName, receiverName, amount)
-- @see ix.log.AddType
-- @see ix.log.AddRaw
function ix.log.Add(client, logType, ...)
local logString, logFlag = ix.log.Parse(client, logType, ...)
if (logString == -1) then return end
CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers)
ix.log.Send(receivers, logString, logFlag)
end)
Msg("[LOG] ", logString .. "\n")
ix.log.CallHandler("Write", client, logString, logFlag, logType, {...})
end
--- Sends a log entry to clients.
-- @internal
-- @realm server
-- @tparam Player|table receivers Target player(s).
-- @tparam string logString Log message.
-- @tparam[opt] number flag `FLAG_*` value (defaults to normal).
function ix.log.Send(receivers, logString, flag)
net.Start("ixLogStream")
net.WriteString(logString)
net.WriteUInt(flag or 0, 4)
net.Send(receivers)
end
ix.log.handlers = ix.log.handlers or {}
--- Calls a specific event on all registered log handlers.
-- @realm server
-- @internal
function ix.log.CallHandler(event, ...)
for _, v in pairs(ix.log.handlers) do
if (isfunction(v[event])) then
v[event](...)
end
end
end
-- Register a log handler
-- @realm server
-- @internal
function ix.log.RegisterHandler(name, data)
data.name = string.gsub(name, "%s", "")
name = name:lower()
data.uniqueID = name
ix.log.handlers[name] = data
end
do
local HANDLER = {}
function HANDLER.Load()
file.CreateDir("helix/logs")
end
function HANDLER.Write(client, message)
file.Append("helix/logs/" .. os.date("%x"):gsub("/", "-") .. ".txt", "[" .. os.date("%X") .. "]\t" .. message .. "\r\n")
end
ix.log.RegisterHandler("File", HANDLER)
end
else
net.Receive("ixLogStream", function(length)
local logString = net.ReadString()
local flag = net.ReadUInt(4)
if (isstring(logString) and isnumber(flag)) then
MsgC(consoleColor, "[SERVER] ", ix.log.color[flag], logString .. "\n")
end
end)
end
================================================
FILE: gamemode/core/libs/sh_menu.lua
================================================
--[[--
Entity menu manipulation.
The `menu` library allows you to open up a context menu of arbitrary options whose callbacks will be ran when they are selected
from the panel that shows up for the player.
]]
-- @module ix.menu
--- You'll need to pass a table of options to `ix.menu.Open` to populate the menu. This table consists of strings as its keys
-- and functions as its values. These correspond to the text displayed in the menu and the callback to run, respectively.
--
-- Example usage:
-- ix.menu.Open({
-- Drink = function()
-- print("Drink option selected!")
-- end,
-- Take = function()
-- print("Take option selected!")
-- end
-- }, ents.GetByIndex(1))
-- This opens a menu with the options `"Drink"` and `"Take"` which will print a message when you click on either of the options.
-- @realm client
-- @table MenuOptionsStructure
ix.menu = ix.menu or {}
if (CLIENT) then
--- Opens up a context menu for the given entity.
-- @realm client
-- @tparam MenuOptionsStructure options Data describing what options to display
-- @entity[opt] entity Entity to send commands to
-- @treturn boolean Whether or not the menu opened successfully. It will fail when there is already a menu open.
function ix.menu.Open(options, entity)
if (IsValid(ix.menu.panel)) then
return false
end
local panel = vgui.Create("ixEntityMenu")
panel:SetEntity(entity)
panel:SetOptions(options)
return true
end
--- Checks whether or not an entity menu is currently open.
-- @realm client
-- @treturn boolean Whether or not an entity menu is open
function ix.menu.IsOpen()
return IsValid(ix.menu.panel)
end
--- Notifies the server of an option that was chosen for the given entity.
-- @realm client
-- @entity entity Entity to call option on
-- @string choice Option that was chosen
-- @param data Extra data to send to the entity
function ix.menu.NetworkChoice(entity, choice, data)
if (IsValid(entity)) then
net.Start("ixEntityMenuSelect")
net.WriteEntity(entity)
net.WriteString(choice)
net.WriteType(data)
net.SendToServer()
end
end
else
util.AddNetworkString("ixEntityMenuSelect")
net.Receive("ixEntityMenuSelect", function(length, client)
local entity = net.ReadEntity()
local option = net.ReadString()
local data = net.ReadType()
if (!IsValid(entity)
or !isstring(option)
or hook.Run("CanPlayerInteractEntity", client, entity, option, data) == false
) then
return
end
hook.Run("PlayerInteractEntity", client, entity, option, data)
local callbackName = "OnSelect" .. option:gsub("%s", "")
if (entity[callbackName]) then
entity[callbackName](entity, client, data)
else
entity:OnOptionSelected(client, option, data)
end
end)
end
do
local PLAYER = FindMetaTable("Player")
if (CLIENT) then
function PLAYER:GetEntityMenu()
local options = {}
hook.Run("GetPlayerEntityMenu", self, options)
return options
end
else
function PLAYER:OnOptionSelected(client, option)
hook.Run("OnPlayerOptionSelected", self, client, option)
end
end
end
================================================
FILE: gamemode/core/libs/sh_notice.lua
================================================
--- Notification helper functions
-- @module ix.notice
if (SERVER) then
util.AddNetworkString("ixNotify")
util.AddNetworkString("ixNotifyLocalized")
--- Sends a notification to a specified recipient.
-- @realm server
-- @string message Message to notify
-- @player[opt=nil] recipient Player to be notified
function ix.util.Notify(message, recipient)
net.Start("ixNotify")
net.WriteString(message)
if (recipient == nil) then
net.Broadcast()
else
net.Send(recipient)
end
end
--- Sends a translated notification to a specified recipient.
-- @realm server
-- @string message Message to notify
-- @player[opt=nil] recipient Player to be notified
-- @param ... Arguments to pass to the translated message
function ix.util.NotifyLocalized(message, recipient, ...)
net.Start("ixNotifyLocalized")
net.WriteString(message)
net.WriteTable({...})
if (recipient == nil) then
net.Broadcast()
else
net.Send(recipient)
end
end
do
--- Notification util functions for players
-- @classmod Player
local playerMeta = FindMetaTable("Player")
--- Displays a prominent notification in the top-right of this player's screen.
-- @realm shared
-- @string message Text to display in the notification
function playerMeta:Notify(message)
ix.util.Notify(message, self)
end
--- Displays a notification for this player with the given language phrase.
-- @realm shared
-- @string message ID of the phrase to display to the player
-- @param ... Arguments to pass to the phrase
-- @usage client:NotifyLocalized("mapRestarting", 10)
-- -- displays "The map will restart in 10 seconds!" if the player's language is set to English
-- @see ix.lang
function playerMeta:NotifyLocalized(message, ...)
ix.util.NotifyLocalized(message, self, ...)
end
--- Displays a notification for this player in the chatbox.
-- @realm shared
-- @string message Text to display in the notification
function playerMeta:ChatNotify(message)
local messageLength = message:utf8len()
ix.chat.Send(nil, "notice", message, false, {self}, {
bError = message:utf8sub(messageLength, messageLength) == "!"
})
end
--- Displays a notification for this player in the chatbox with the given language phrase.
-- @realm shared
-- @string message ID of the phrase to display to the player
-- @param ... Arguments to pass to the phrase
-- @see NotifyLocalized
function playerMeta:ChatNotifyLocalized(message, ...)
message = L(message, self, ...)
local messageLength = message:utf8len()
ix.chat.Send(nil, "notice", message, false, {self}, {
bError = message:utf8sub(messageLength, messageLength) == "!"
})
end
end
else
-- Create a notification panel.
function ix.util.Notify(message)
if (ix.option.Get("chatNotices", false)) then
local messageLength = message:utf8len()
ix.chat.Send(LocalPlayer(), "notice", message, false, {
bError = message:utf8sub(messageLength, messageLength) == "!"
})
return
end
if (IsValid(ix.gui.notices)) then
ix.gui.notices:AddNotice(message)
end
MsgC(Color(0, 255, 255), message .. "\n")
end
-- Creates a translated notification.
function ix.util.NotifyLocalized(message, ...)
ix.util.Notify(L(message, ...))
end
-- shortcut notify functions
do
local playerMeta = FindMetaTable("Player")
function playerMeta:Notify(message)
if (self == LocalPlayer()) then
ix.util.Notify(message)
end
end
function playerMeta:NotifyLocalized(message, ...)
if (self == LocalPlayer()) then
ix.util.NotifyLocalized(message, ...)
end
end
function playerMeta:ChatNotify(message)
if (self == LocalPlayer()) then
local messageLength = message:utf8len()
ix.chat.Send(LocalPlayer(), "notice", message, false, {
bError = message:utf8sub(messageLength, messageLength) == "!"
})
end
end
function playerMeta:ChatNotifyLocalized(message, ...)
if (self == LocalPlayer()) then
message = L(message, ...)
local messageLength = message:utf8len()
ix.chat.Send(LocalPlayer(), "notice", message, false, {
bError = message:utf8sub(messageLength, messageLength) == "!"
})
end
end
end
-- Receives a notification from the server.
net.Receive("ixNotify", function()
ix.util.Notify(net.ReadString())
end)
-- Receives a notification from the server.
net.Receive("ixNotifyLocalized", function()
ix.util.NotifyLocalized(net.ReadString(), unpack(net.ReadTable()))
end)
end
================================================
FILE: gamemode/core/libs/sh_option.lua
================================================
--[[--
Client-side configuration management.
The `option` library provides a cleaner way to manage any arbitrary data on the client without the hassle of managing CVars. It
is analagous to the `ix.config` library, but it only deals with data that needs to be stored on the client.
To get started, you'll need to define an option in a client realm so the framework can be aware of its existence. This can be
done in the `cl_init.lua` file of your schema, or in an `if (CLIENT) then` statement in the `sh_plugin.lua` file of your plugin:
ix.option.Add("headbob", ix.type.bool, true)
If you need to get the value of an option on the server, you'll need to specify `true` for the `bNetworked` argument in
`ix.option.Add`. *NOTE:* You also need to define your option in a *shared* realm, since the server now also needs to be aware
of its existence. This makes it so that the client will send that option's value to the server whenever it changes, which then
means that the server can now retrieve the value that the client has the option set to. For example, if you need to get what
language a client is using, you can simply do the following:
ix.option.Get(player.GetByID(1), "language", "english")
This will return the language of the player, or `"english"` if one isn't found. Note that `"language"` is a networked option
that is already defined in the framework, so it will always be available. All options will show up in the options menu on the
client, unless `hidden` returns `true` when using `ix.option.Add`.
Note that the labels for each option in the menu will use a language phrase to show the name. For example, if your option is
named `headbob`, then you'll need to define a language phrase called `optHeadbob` that will be used as the option title.
]]
-- @module ix.option
ix.option = ix.option or {}
ix.option.stored = ix.option.stored or {}
ix.option.categories = ix.option.categories or {}
--- Creates a client-side configuration option with the given information.
-- @realm shared
-- @string key Unique ID for this option
-- @ixtype optionType Type of this option
-- @param default Default value that this option will have - this can be nil if needed
-- @tparam OptionStructure data Additional settings for this option
-- @usage ix.option.Add("animationScale", ix.type.number, 1, {
-- category = "appearance",
-- min = 0.3,
-- max = 2,
-- decimals = 1
-- })
function ix.option.Add(key, optionType, default, data)
assert(isstring(key) and key:find("%S"), "expected a non-empty string for the key")
data = data or {}
local oldOption = ix.option.stored[key]
local categories = ix.option.categories
local category = data.category or "misc"
local upperName = key:sub(1, 1):upper() .. key:sub(2)
categories[category] = categories[category] or {}
categories[category][key] = true
-- using explicit nil comparisons so we don't get caught by a config's value being `false`
if (oldOption != nil and oldOption.default != nil) then
default = oldOption.default
end
--- You can specify additional optional arguments for `ix.option.Add` by passing in a table of specific fields as the fourth
-- argument.
-- @table OptionStructure
-- @realm shared
-- @field[type=string,opt="opt" .. key] phrase The phrase to use when displaying in the UI. The default value is your option
-- key in UpperCamelCase, prefixed with `"opt"`. For example, if your key is `"exampleOption"`, the default phrase will be
-- `"optExampleOption"`.
-- @field[type=string,opt="optd" .. key] description The phrase to use in the tooltip when hovered in the UI. The default
-- value is your option key in UpperCamelCase, prefixed with `"optd"`. For example, if your key is `"exampleOption"`, the
-- default phrase will be `"optdExampleOption"`.
-- @field[type=string,opt="misc"] category The category that this option should reside in. This is purely for
-- aesthetic reasons when displaying the options in the options menu. When displayed in the UI, it will take the form of
-- `L("category name")`. This means that you must create a language phrase for the category name - otherwise it will only
-- show as the exact string you've specified. If no category is set, it will default to `"misc"`.
-- @field[type=number,opt=0] min The minimum allowed amount when setting this option. This field is not
-- applicable to any type other than `ix.type.number`.
-- @field[type=number,opt=10] max The maximum allowed amount when setting this option. This field is not
-- applicable to any type other than `ix.type.number`.
-- @field[type=number,opt=0] decimals How many decimals to constrain to when using a number type. This field is not
-- applicable to any type other than `ix.type.number`.
-- @field[type=boolean,opt=false] bNetworked Whether or not the server should be aware of this option for each client.
-- @field[type=function,opt] OnChanged The function to run when this option is changed - this includes whether it was set
-- by the player, or through code using `ix.option.Set`.
-- OnChanged = function(oldValue, value)
-- print("new value is", value)
-- end
-- @field[type=function,opt] hidden The function to check whether the option should be hidden from the options menu.
-- @field[type=function,opt] populate The function to run when the option needs to be added to the menu. This is a required
-- field for any array options. It should return a table of entries where the key is the value to set in `ix.option.Set`,
-- and the value is the display name for the entry. An example:
--
-- populate = function()
-- return {
-- ["english"] = "English",
-- ["french"] = "French",
-- ["spanish"] = "Spanish"
-- }
-- end
ix.option.stored[key] = {
key = key,
phrase = data.phrase or "opt" .. upperName,
description = data.description or "optd" .. upperName,
type = optionType,
default = default,
min = data.min or 0,
max = data.max or 10,
decimals = data.decimals or 0,
category = data.category or "misc",
bNetworked = data.bNetworked and true or false,
hidden = data.hidden or nil,
populate = data.populate or nil,
OnChanged = data.OnChanged or nil
}
end
--- Sets the default value for a user option.
-- @realm shared
-- @string key Unique ID of the option
-- @param value Default value for the user option
function ix.option.SetDefault(key, value)
local option = ix.option.stored[key]
if (option) then
option.default = value
else
-- set up dummy option if we're setting default of option that doesn't exist yet (i.e schema setting framework default)
ix.option.stored[key] = {
default = value
}
end
end
--- Loads all saved options from disk.
-- @realm shared
-- @internal
function ix.option.Load()
ix.util.Include("helix/gamemode/config/sh_options.lua")
if (CLIENT) then
local options = ix.data.Get("options", nil, true, true)
if (options) then
for k, v in pairs(options) do
ix.option.client[k] = v
end
end
ix.option.Sync()
end
end
--- Returns all of the available options. Note that this does contain the actual values of the options, just their properties.
-- @realm shared
-- @treturn table Table of all options
-- @usage PrintTable(ix.option.GetAll())
-- > language:
-- > bNetworked = true
-- > default = english
-- > type = 512
-- -- etc.
function ix.option.GetAll()
return ix.option.stored
end
--- Returns all of the available options grouped by their categories. The returned table contains category tables, that contain
-- all the options in that category as an array (this is so you can sort them if you'd like).
-- @realm shared
-- @bool[opt=false] bRemoveHidden Remove entries that are marked as hidden
-- @treturn table Table of all options
-- @usage PrintTable(ix.option.GetAllByCategories())
-- > general:
-- > 1:
-- > key = language
-- > bNetworked = true
-- > default = english
-- > type = 512
-- -- etc.
function ix.option.GetAllByCategories(bRemoveHidden)
local result = {}
for k, v in pairs(ix.option.categories) do
for k2, _ in pairs(v) do
local option = ix.option.stored[k2]
if (bRemoveHidden and isfunction(option.hidden) and option.hidden()) then
continue
end
-- we create the category table here because it could contain all hidden options which makes the table empty
result[k] = result[k] or {}
result[k][#result[k] + 1] = option
end
end
return result
end
if (CLIENT) then
ix.option.client = ix.option.client or {}
--- Sets an option value for the local player.
-- This function will error when an invalid key is passed.
-- @realm client
-- @string key Unique ID of the option
-- @param value New value to assign to the option
-- @bool[opt=false] bNoSave Whether or not to avoid saving
function ix.option.Set(key, value, bNoSave)
local option = assert(ix.option.stored[key], "invalid option key \"" .. tostring(key) .. "\"")
if (option.type == ix.type.number) then
value = math.Clamp(math.Round(value, option.decimals), option.min, option.max)
end
local oldValue = ix.option.client[key]
ix.option.client[key] = value
if (option.bNetworked) then
net.Start("ixOptionSet")
net.WriteString(key)
net.WriteType(value)
net.SendToServer()
end
if (!bNoSave) then
ix.option.Save()
end
if (isfunction(option.OnChanged)) then
option.OnChanged(oldValue, value)
end
end
--- Retrieves an option value for the local player. If it is not set, it'll return the default that you've specified.
-- @realm client
-- @string key Unique ID of the option
-- @param default Default value to return if the option is not set
-- @return[1] Value associated with the key
-- @return[2] The given default if the option is not set
function ix.option.Get(key, default)
local option = ix.option.stored[key]
if (option) then
local localValue = ix.option.client[key]
if (localValue != nil) then
return localValue
end
return option.default
end
return default
end
--- Saves all options to disk.
-- @realm client
-- @internal
function ix.option.Save()
ix.data.Set("options", ix.option.client, true, true)
end
--- Syncs all networked options to the server.
-- @realm client
function ix.option.Sync()
local options = {}
for k, v in pairs(ix.option.stored) do
if (v.bNetworked) then
options[#options + 1] = {k, ix.option.client[k]}
end
end
if (#options > 0) then
net.Start("ixOptionSync")
net.WriteUInt(#options, 8)
for _, v in ipairs(options) do
net.WriteString(v[1])
net.WriteType(v[2])
end
net.SendToServer()
end
end
else
util.AddNetworkString("ixOptionSet")
util.AddNetworkString("ixOptionSync")
ix.option.clients = ix.option.clients or {}
--- Retrieves an option value from the specified player. If it is not set, it'll return the default that you've specified.
-- This function will error when an invalid player is passed.
-- @realm server
-- @player client Player to retrieve option value from
-- @string key Unique ID of the option
-- @param default Default value to return if the option is not set
-- @return[1] Value associated with the key
-- @return[2] The given default if the option is not set
function ix.option.Get(client, key, default)
assert(IsValid(client) and client:IsPlayer(), "expected valid player for argument #1")
local option = ix.option.stored[key]
if (option) then
local clientOptions = ix.option.clients[client:SteamID64()]
if (clientOptions) then
local clientOption = clientOptions[key]
if (clientOption != nil) then
return clientOption
end
end
return option.default
end
return default
end
-- sent whenever a client's networked option has changed
net.Receive("ixOptionSet", function(length, client)
local key = net.ReadString()
local value = net.ReadType()
local steamID = client:SteamID64()
local option = ix.option.stored[key]
if (option) then
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
ix.option.clients[steamID][key] = value
else
ErrorNoHalt(string.format(
"'%s' attempted to set option with invalid key '%s'\n", tostring(client) .. client:SteamID(), key
))
end
end)
-- sent on first load to sync all networked option values
net.Receive("ixOptionSync", function(length, client)
local indices = net.ReadUInt(8)
local data = {}
for _ = 1, indices do
data[net.ReadString()] = net.ReadType()
end
local steamID = client:SteamID64()
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
for k, v in pairs(data) do
local option = ix.option.stored[k]
if (option) then
ix.option.clients[steamID][k] = v
else
return ErrorNoHalt(string.format(
"'%s' attempted to sync option with invalid key '%s'\n", tostring(client) .. client:SteamID(), k
))
end
end
end)
end
================================================
FILE: gamemode/core/libs/sh_player.lua
================================================
local playerMeta = FindMetaTable("Player")
-- ixData information for the player.
do
if (SERVER) then
function playerMeta:GetData(key, default)
if (key == true) then
return self.ixData
end
local data = self.ixData and self.ixData[key]
if (data == nil) then
return default
else
return data
end
end
else
function playerMeta:GetData(key, default)
local data = ix.localData and ix.localData[key]
if (data == nil) then
return default
else
return data
end
end
net.Receive("ixDataSync", function()
ix.localData = net.ReadTable()
ix.playTime = net.ReadUInt(32)
end)
net.Receive("ixData", function()
ix.localData = ix.localData or {}
ix.localData[net.ReadString()] = net.ReadType()
end)
end
end
-- Whitelist networking information here.
do
function playerMeta:HasWhitelist(faction)
local data = ix.faction.indices[faction]
if (data) then
if (data.isDefault) then
return true
end
local ixData = self:GetData("whitelists", {})
return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false
end
return false
end
function playerMeta:GetItems()
local char = self:GetCharacter()
if (char) then
local inv = char:GetInventory()
if (inv) then
return inv:GetItems()
end
end
end
function playerMeta:GetClassData()
local char = self:GetCharacter()
if (char) then
local class = char:GetClass()
if (class) then
local classData = ix.class.list[class]
return classData
end
end
end
end
do
if (SERVER) then
util.AddNetworkString("PlayerModelChanged")
util.AddNetworkString("PlayerSelectWeapon")
local entityMeta = FindMetaTable("Entity")
entityMeta.ixSetModel = entityMeta.ixSetModel or entityMeta.SetModel
playerMeta.ixSelectWeapon = playerMeta.ixSelectWeapon or playerMeta.SelectWeapon
function entityMeta:SetModel(model)
local oldModel = self:GetModel()
if (self:IsPlayer()) then
hook.Run("PlayerModelChanged", self, model, oldModel)
net.Start("PlayerModelChanged")
net.WriteEntity(self)
net.WriteString(model)
net.WriteString(oldModel)
net.Broadcast()
end
return self:ixSetModel(model)
end
function playerMeta:SelectWeapon(className)
net.Start("PlayerSelectWeapon")
net.WriteEntity(self)
net.WriteString(className)
net.Broadcast()
return self:ixSelectWeapon(className)
end
else
net.Receive("PlayerModelChanged", function(length)
hook.Run("PlayerModelChanged", net.ReadEntity(), net.ReadString(), net.ReadString())
end)
net.Receive("PlayerSelectWeapon", function(length)
local client = net.ReadEntity()
local className = net.ReadString()
if (!IsValid(client)) then
hook.Run("PlayerWeaponChanged", client, NULL)
return
end
for _, v in ipairs(client:GetWeapons()) do
if (v:GetClass() == className) then
hook.Run("PlayerWeaponChanged", client, v)
break
end
end
end)
end
end
================================================
FILE: gamemode/core/libs/sh_plugin.lua
================================================
ix.plugin = ix.plugin or {}
ix.plugin.list = ix.plugin.list or {}
ix.plugin.unloaded = ix.plugin.unloaded or {}
ix.util.Include("helix/gamemode/core/meta/sh_tool.lua")
-- luacheck: globals HOOKS_CACHE
HOOKS_CACHE = {}
function ix.plugin.Load(uniqueID, path, isSingleFile, variable)
if (hook.Run("PluginShouldLoad", uniqueID) == false) then return end
variable = variable or "PLUGIN"
-- Plugins within plugins situation?
local oldPlugin = PLUGIN
local PLUGIN = {
folder = path,
plugin = oldPlugin,
uniqueID = uniqueID,
name = "Unknown",
description = "Description not available",
author = "Anonymous"
}
if (uniqueID == "schema") then
if (Schema) then
PLUGIN = Schema
end
variable = "Schema"
PLUGIN.folder = engine.ActiveGamemode()
elseif (ix.plugin.list[uniqueID]) then
PLUGIN = ix.plugin.list[uniqueID]
end
_G[variable] = PLUGIN
PLUGIN.loading = true
if (!isSingleFile) then
ix.lang.LoadFromDir(path.."/languages")
ix.util.IncludeDir(path.."/libs", true)
ix.attributes.LoadFromDir(path.."/attributes")
ix.faction.LoadFromDir(path.."/factions")
ix.class.LoadFromDir(path.."/classes")
ix.item.LoadFromDir(path.."/items")
ix.plugin.LoadFromDir(path.."/plugins")
ix.util.IncludeDir(path.."/derma", true)
ix.plugin.LoadEntities(path.."/entities")
hook.Run("DoPluginIncludes", path, PLUGIN)
end
ix.util.Include(isSingleFile and path or path.."/sh_"..variable:lower()..".lua", "shared")
PLUGIN.loading = false
local uniqueID2 = uniqueID
if (uniqueID2 == "schema") then
uniqueID2 = PLUGIN.name
end
function PLUGIN:SetData(value, global, ignoreMap)
ix.data.Set(uniqueID2, value, global, ignoreMap)
end
function PLUGIN:GetData(default, global, ignoreMap, refresh)
return ix.data.Get(uniqueID2, default, global, ignoreMap, refresh) or {}
end
hook.Run("PluginLoaded", uniqueID, PLUGIN)
if (uniqueID != "schema") then
PLUGIN.name = PLUGIN.name or "Unknown"
PLUGIN.description = PLUGIN.description or "No description available."
for k, v in pairs(PLUGIN) do
if (isfunction(v)) then
HOOKS_CACHE[k] = HOOKS_CACHE[k] or {}
HOOKS_CACHE[k][PLUGIN] = v
end
end
ix.plugin.list[uniqueID] = PLUGIN
_G[variable] = oldPlugin
end
if (PLUGIN.OnLoaded) then
PLUGIN:OnLoaded()
end
end
function ix.plugin.GetHook(pluginName, hookName)
local h = HOOKS_CACHE[hookName]
if (h) then
local p = ix.plugin.list[pluginName]
if (p) then
return h[p]
end
end
return
end
function ix.plugin.LoadEntities(path)
local bLoadedTools
local files, folders
local function IncludeFiles(path2, bClientOnly)
if (SERVER and !bClientOnly) then
if (file.Exists(path2.."init.lua", "LUA")) then
ix.util.Include(path2.."init.lua", "server")
elseif (file.Exists(path2.."shared.lua", "LUA")) then
ix.util.Include(path2.."shared.lua")
end
if (file.Exists(path2.."cl_init.lua", "LUA")) then
ix.util.Include(path2.."cl_init.lua", "client")
end
elseif (file.Exists(path2.."cl_init.lua", "LUA")) then
ix.util.Include(path2.."cl_init.lua", "client")
elseif (file.Exists(path2.."shared.lua", "LUA")) then
ix.util.Include(path2.."shared.lua")
end
end
local function HandleEntityInclusion(folder, variable, register, default, clientOnly, create, complete)
files, folders = file.Find(path.."/"..folder.."/*", "LUA")
default = default or {}
for _, v in ipairs(folders) do
local path2 = path.."/"..folder.."/"..v.."/"
v = ix.util.StripRealmPrefix(v)
_G[variable] = table.Copy(default)
if (!isfunction(create)) then
_G[variable].ClassName = v
else
create(v)
end
IncludeFiles(path2, clientOnly)
if (clientOnly) then
if (CLIENT) then
register(_G[variable], v)
end
else
register(_G[variable], v)
end
if (isfunction(complete)) then
complete(_G[variable])
end
_G[variable] = nil
end
for _, v in ipairs(files) do
local niceName = ix.util.StripRealmPrefix(string.StripExtension(v))
_G[variable] = table.Copy(default)
if (!isfunction(create)) then
_G[variable].ClassName = niceName
else
create(niceName)
end
ix.util.Include(path.."/"..folder.."/"..v, clientOnly and "client" or "shared")
if (clientOnly) then
if (CLIENT) then
register(_G[variable], niceName)
end
else
register(_G[variable], niceName)
end
if (isfunction(complete)) then
complete(_G[variable])
end
_G[variable] = nil
end
end
local function RegisterTool(tool, className)
local gmodTool = weapons.GetStored("gmod_tool")
if (className:sub(1, 3) == "sh_") then
className = className:sub(4)
end
if (gmodTool) then
gmodTool.Tool[className] = tool
else
-- this should never happen
ErrorNoHalt(string.format("attempted to register tool '%s' with invalid gmod_tool weapon", className))
end
bLoadedTools = true
end
-- Include entities.
HandleEntityInclusion("entities", "ENT", scripted_ents.Register, {
Type = "anim",
Base = "base_gmodentity",
Spawnable = true
}, false, nil, function(ent)
if (SERVER and ent.Holdable == true) then
ix.allowedHoldableClasses[ent.ClassName] = true
end
end)
-- Include weapons.
HandleEntityInclusion("weapons", "SWEP", weapons.Register, {
Primary = {},
Secondary = {},
Base = "weapon_base"
})
HandleEntityInclusion("tools", "TOOL", RegisterTool, {}, false, function(className)
if (className:sub(1, 3) == "sh_") then
className = className:sub(4)
end
TOOL = ix.meta.tool:Create()
TOOL.Mode = className
TOOL:CreateConVars()
end)
-- Include effects.
HandleEntityInclusion("effects", "EFFECT", effects and effects.Register, nil, true)
-- only reload spawn menu if any new tools were registered
if (CLIENT and bLoadedTools) then
RunConsoleCommand("spawnmenu_reload")
end
end
function ix.plugin.Initialize()
if SERVER then
ix.plugin.unloaded = ix.data.Get("unloaded", {}, true, true)
end
ix.plugin.LoadFromDir("helix/plugins")
ix.plugin.Load("schema", engine.ActiveGamemode().."/schema")
hook.Run("InitializedSchema")
ix.plugin.LoadFromDir(engine.ActiveGamemode().."/plugins")
hook.Run("InitializedPlugins")
end
function ix.plugin.Get(identifier)
return ix.plugin.list[identifier]
end
function ix.plugin.LoadFromDir(directory)
local files, folders = file.Find(directory.."/*", "LUA")
for _, v in ipairs(folders) do
ix.plugin.Load(v, directory.."/"..v)
end
for _, v in ipairs(files) do
ix.plugin.Load(string.StripExtension(v), directory.."/"..v, true)
end
end
function ix.plugin.SetUnloaded(uniqueID, state, bNoSave)
local plugin = ix.plugin.list[uniqueID]
if (state) then
if (plugin and plugin.OnUnload) then
plugin:OnUnload()
end
ix.plugin.unloaded[uniqueID] = true
elseif (ix.plugin.unloaded[uniqueID]) then
ix.plugin.unloaded[uniqueID] = false
else
return false
end
if (SERVER and !bNoSave) then
local status
if (state) then
status = true
end
local unloaded = ix.data.Get("unloaded", {}, true, true)
unloaded[uniqueID] = status
ix.data.Set("unloaded", unloaded, true, true)
end
if (state) then
hook.Run("PluginUnloaded", uniqueID)
end
return true
end
if (SERVER) then
--- Runs the `LoadData` and `PostLoadData` hooks for the gamemode, schema, and plugins. Any plugins that error during the
-- hook will have their `SaveData` and `PostLoadData` hooks removed to prevent them from saving junk data.
-- @internal
-- @realm server
function ix.plugin.RunLoadData()
local errors = hook.SafeRun("LoadData")
-- remove the SaveData and PostLoadData hooks for any plugins that error during LoadData since they would probably be
-- saving bad data. this doesn't prevent plugins from saving data via other means, but there's only so much we can do
for _, v in pairs(errors or {}) do
if (v.plugin) then
local plugin = ix.plugin.Get(v.plugin)
if (plugin) then
local saveDataHooks = HOOKS_CACHE["SaveData"] or {}
saveDataHooks[plugin] = nil
local postLoadDataHooks = HOOKS_CACHE["PostLoadData"] or {}
postLoadDataHooks[plugin] = nil
end
end
end
hook.Run("PostLoadData")
end
end
do
-- luacheck: globals hook
hook.ixCall = hook.ixCall or hook.Call
function hook.Call(name, gm, ...)
local cache = HOOKS_CACHE[name]
if (cache) then
for k, v in pairs(cache) do
local a, b, c, d, e, f = v(k, ...)
if (a != nil) then
return a, b, c, d, e, f
end
end
end
if (Schema and Schema[name]) then
local a, b, c, d, e, f = Schema[name](Schema, ...)
if (a != nil) then
return a, b, c, d, e, f
end
end
return hook.ixCall(name, gm, ...)
end
--- Runs the given hook in a protected call so that the calling function will continue executing even if any errors occur
-- while running the hook. This function is much more expensive to call than `hook.Run`, so you should avoid using it unless
-- you absolutely need to avoid errors from stopping the execution of your function.
-- @internal
-- @realm shared
-- @string name Name of the hook to run
-- @param ... Arguments to pass to the hook functions
-- @treturn[1] table Table of error data if an error occurred while running
-- @treturn[1] ... Any arguments returned by the hook functions
-- @usage local errors, bCanSpray = hook.SafeRun("PlayerSpray", Entity(1))
-- if (!errors) then
-- -- do stuff with bCanSpray
-- else
-- PrintTable(errors)
-- end
function hook.SafeRun(name, ...)
local errors = {}
local gm = gmod and gmod.GetGamemode() or nil
local cache = HOOKS_CACHE[name]
if (cache) then
for k, v in pairs(cache) do
local bSuccess, a, b, c, d, e, f = pcall(v, k, ...)
if (bSuccess) then
if (a != nil) then
return errors, a, b, c, d, e, f
end
else
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for plugin hook \"%s:%s\":\n\t%s\n%s\n",
tostring(k and k.uniqueID or nil), tostring(name), tostring(a), debug.traceback()))
errors[#errors + 1] = {
name = name,
plugin = k and k.uniqueID or nil,
errorMessage = tostring(a)
}
end
end
end
if (Schema and Schema[name]) then
local bSuccess, a, b, c, d, e, f = pcall(Schema[name], Schema, ...)
if (bSuccess) then
if (a != nil) then
return errors, a, b, c, d, e, f
end
else
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for schema hook \"%s\":\n\t%s\n%s\n",
tostring(name), tostring(a), debug.traceback()))
errors[#errors + 1] = {
name = name,
schema = Schema.name,
errorMessage = tostring(a)
}
end
end
local bSuccess, a, b, c, d, e, f = pcall(hook.ixCall, name, gm, ...)
if (bSuccess) then
return errors, a, b, c, d, e, f
else
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for gamemode hook \"%s\":\n\t%s\n%s\n",
tostring(name), tostring(a), debug.traceback()))
errors[#errors + 1] = {
name = name,
gamemode = "gamemode",
errorMessage = tostring(a)
}
return errors
end
end
end
================================================
FILE: gamemode/core/libs/sh_storage.lua
================================================
--[[--
Player manipulation of inventories.
This library provides an easy way for players to manipulate other inventories. The only functions that you should need are
`ix.storage.Open` and `ix.storage.Close`. When opening an inventory as a storage item, it will display both the given inventory
and the player's inventory in the player's UI, which allows them to drag items to and from the given inventory.
Example usage:
ix.storage.Open(client, inventory, {
name = "Filing Cabinet",
entity = ents.GetByIndex(3),
bMultipleUsers = true,
searchText = "Rummaging...",
searchTime = 4
})
]]
-- @module ix.storage
--- There are some parameters you can customize when opening an inventory as a storage object with `ix.storage.Open`.
-- @realm server
-- @table StorageInfoStructure
-- @field[type=entity] entity Entity to "attach" the inventory to. This is used to provide a location for the inventory for
-- things like making sure the player doesn't move too far away from the inventory, etc. This can also be a `player` object.
-- @field[type=number,opt=inventory id] id The ID of the nventory. This defaults to the inventory passed into `ix.Storage.Open`.
-- @field[type=string,opt="Storage"] name Title to display in the UI when the inventory is open.
-- @field[type=boolean,opt=false] bMultipleUsers Whether or not multiple players are allowed to view this inventory at the
-- same time.
-- @field[type=number,opt=0] searchTime How long the player has to wait before the inventory is opened.
-- @field[type=string,opt="@storageSearching"] text Text to display to the user while opening the inventory. If prefixed with
-- `"@"`, it will display a language phrase.
-- @field[type=function,opt] OnPlayerClose Called when a player who was accessing the inventory has closed it. The
-- argument passed to the callback is the player who closed it.
-- @field[type=table,opt={}] data Table of arbitrary data to send to the client when the inventory has been opened.
ix.storage = ix.storage or {}
if (SERVER) then
util.AddNetworkString("ixStorageOpen")
util.AddNetworkString("ixStorageClose")
util.AddNetworkString("ixStorageExpired")
util.AddNetworkString("ixStorageMoneyTake")
util.AddNetworkString("ixStorageMoneyGive")
util.AddNetworkString("ixStorageMoneyUpdate")
--- Returns whether or not the given inventory has a storage context and is being looked at by other players.
-- @realm server
-- @inventory inventory Inventory to check
-- @treturn bool Whether or not `inventory` is in use
function ix.storage.InUse(inventory)
if (inventory.storageInfo) then
for _, v in pairs(inventory:GetReceivers()) do
if (IsValid(v) and v:IsPlayer() and v != inventory.storageInfo.entity) then
return true
end
end
end
return false
end
--- Returns whether or not an inventory is in use by a specific player.
-- @realm server
-- @inventory inventory Inventory to check
-- @player client Player to check
-- @treturn bool Whether or not the player is using the given `inventory`
function ix.storage.InUseBy(inventory, client)
if (inventory.storageInfo) then
for _, v in pairs(inventory:GetReceivers()) do
if (IsValid(v) and v:IsPlayer() and v == client) then
return true
end
end
end
return false
end
--- Creates a storage context on the given inventory.
-- @realm server
-- @internal
-- @inventory inventory Inventory to create a storage context for
-- @tab info Information to store on the context
function ix.storage.CreateContext(inventory, info)
info = info or {}
info.id = inventory:GetID()
info.name = info.name or "Storage"
info.entity = assert(IsValid(info.entity), "expected valid entity in info table") and info.entity
info.bMultipleUsers = info.bMultipleUsers == nil and false or info.bMultipleUsers
info.searchTime = tonumber(info.searchTime) or 0
info.searchText = info.searchText or "@storageSearching"
info.data = info.data or {}
inventory.storageInfo = info
-- remove context from any bags this inventory might have
for k, _ in inventory:Iter() do
if (k.isBag and k:GetInventory()) then
ix.storage.CreateContext(k:GetInventory(), table.Copy(info))
end
end
end
--- Removes a storage context from an inventory if it exists.
-- @realm server
-- @internal
-- @inventory inventory Inventory to remove a storage context from
function ix.storage.RemoveContext(inventory)
inventory.storageInfo = nil
-- remove context from any bags this inventory might have
for k, _ in inventory:Iter() do
if (k.isBag and k:GetInventory()) then
ix.storage.RemoveContext(k:GetInventory())
end
end
end
--- Synchronizes an inventory with a storage context to the given client.
-- @realm server
-- @internal
-- @player client Player to sync storage for
-- @inventory inventory Inventory to sync storage for
function ix.storage.Sync(client, inventory)
local info = inventory.storageInfo
-- we'll retrieve the money value as we're syncing because it may have changed while
-- we were waiting for the timer to finish
if (info.entity.GetMoney) then
info.data.money = info.entity:GetMoney()
elseif (info.entity:IsPlayer() and info.entity:GetCharacter()) then
info.data.money = info.entity:GetCharacter():GetMoney()
end
-- bags are automatically sync'd when the owning inventory is sync'd
inventory:Sync(client)
net.Start("ixStorageOpen")
net.WriteUInt(info.id, 32)
net.WriteEntity(info.entity)
net.WriteString(info.name)
net.WriteTable(info.data)
net.Send(client)
end
--- Adds a receiver to a given inventory with a storage context.
-- @realm server
-- @internal
-- @player client Player to sync storage for
-- @inventory inventory Inventory to sync storage for
-- @bool bDontSync Whether or not to skip syncing the storage to the client. If this is `true`, the storage panel will not
-- show up for the player
function ix.storage.AddReceiver(client, inventory, bDontSync)
local info = inventory.storageInfo
if (info) then
inventory:AddReceiver(client)
client.ixOpenStorage = inventory
-- update receivers for any bags this inventory might have
for k, _ in inventory:Iter() do
if (k.isBag and k:GetInventory()) then
k:GetInventory():AddReceiver(client)
end
end
if (isfunction(info.OnPlayerOpen)) then
info.OnPlayerOpen(client)
end
if (!bDontSync) then
ix.storage.Sync(client, inventory)
end
return true
end
return false
end
--- Removes a storage receiver and removes the context if there are no more receivers.
-- @realm server
-- @internal
-- @player client Player to remove from receivers
-- @inventory inventory Inventory with storage context to remove receiver from
-- @bool bDontRemove Whether or not to skip removing the storage context if there are no more receivers
function ix.storage.RemoveReceiver(client, inventory, bDontRemove)
local info = inventory.storageInfo
if (info) then
inventory:RemoveReceiver(client)
-- update receivers for any bags this inventory might have
for k, _ in inventory:Iter() do
if (k.isBag and k:GetInventory()) then
k:GetInventory():RemoveReceiver(client)
end
end
if (isfunction(info.OnPlayerClose)) then
info.OnPlayerClose(client)
end
if (!bDontRemove and !ix.storage.InUse(inventory)) then
ix.storage.RemoveContext(inventory)
end
client.ixOpenStorage = nil
return true
end
return false
end
--- Makes a player open an inventory that they can interact with. This can be called multiple times on the same inventory,
-- if the info passed allows for multiple users.
-- @realm server
-- @player client Player to open the inventory for
-- @inventory inventory Inventory to open
-- @tab info `StorageInfoStructure` describing the storage properties
function ix.storage.Open(client, inventory, info)
assert(IsValid(client) and client:IsPlayer(), "expected valid player")
assert(type(inventory) == "table" and inventory:IsInstanceOf(ix.meta.inventory), "expected valid inventory")
-- create storage context if one isn't already created
if (!inventory.storageInfo) then
info = info or {}
ix.storage.CreateContext(inventory, info)
end
local storageInfo = inventory.storageInfo
-- add the client to the list of receivers if we're allowed to have multiple users
-- or if nobody else is occupying this inventory, otherwise nag the player
if (storageInfo.bMultipleUsers or !ix.storage.InUse(inventory)) then
ix.storage.AddReceiver(client, inventory, true)
else
client:NotifyLocalized("storageInUse")
return
end
if (storageInfo.searchTime > 0) then
client:SetAction(storageInfo.searchText, storageInfo.searchTime)
client:DoStaredAction(storageInfo.entity, function()
if (IsValid(client) and IsValid(storageInfo.entity) and inventory.storageInfo) then
ix.storage.Sync(client, inventory)
end
end, storageInfo.searchTime, function()
if (IsValid(client)) then
ix.storage.RemoveReceiver(client, inventory)
client:SetAction()
end
end)
else
ix.storage.Sync(client, inventory)
end
end
--- Forcefully makes clients close this inventory if they have it open.
-- @realm server
-- @inventory inventory Inventory to close
function ix.storage.Close(inventory)
local receivers = inventory:GetReceivers()
if (#receivers > 0) then
net.Start("ixStorageExpired")
net.WriteUInt(inventory.storageInfo.id, 32)
net.Send(receivers)
end
ix.storage.RemoveContext(inventory)
end
net.Receive("ixStorageClose", function(length, client)
local inventory = client.ixOpenStorage
if (inventory) then
ix.storage.RemoveReceiver(client, inventory)
end
end)
net.Receive("ixStorageMoneyTake", function(length, client)
if (CurTime() < (client.ixStorageMoneyTimer or 0)) then
return
end
local character = client:GetCharacter()
if (!character) then
return
end
local storageID = net.ReadUInt(32)
local amount = net.ReadUInt(32)
local inventory = client.ixOpenStorage
if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then
return
end
local entity = inventory.storageInfo.entity
if (!IsValid(entity) or
(!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or
(entity:IsPlayer() and !entity:GetCharacter())) then
return
end
entity = entity:IsPlayer() and entity:GetCharacter() or entity
amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, entity:GetMoney())
if (amount == 0) then
return
end
character:SetMoney(character:GetMoney() + amount)
local total = entity:GetMoney() - amount
entity:SetMoney(total)
net.Start("ixStorageMoneyUpdate")
net.WriteUInt(storageID, 32)
net.WriteUInt(total, 32)
net.Send(inventory:GetReceivers())
ix.log.Add(client, "storageMoneyTake", entity, amount, total)
client.ixStorageMoneyTimer = CurTime() + 0.5
end)
net.Receive("ixStorageMoneyGive", function(length, client)
if (CurTime() < (client.ixStorageMoneyTimer or 0)) then
return
end
local character = client:GetCharacter()
if (!character) then
return
end
local storageID = net.ReadUInt(32)
local amount = net.ReadUInt(32)
local inventory = client.ixOpenStorage
if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then
return
end
local entity = inventory.storageInfo.entity
if (!IsValid(entity) or
(!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or
(entity:IsPlayer() and !entity:GetCharacter())) then
return
end
entity = entity:IsPlayer() and entity:GetCharacter() or entity
amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, character:GetMoney())
if (amount == 0) then
return
end
character:SetMoney(character:GetMoney() - amount)
local total = entity:GetMoney() + amount
entity:SetMoney(total)
net.Start("ixStorageMoneyUpdate")
net.WriteUInt(storageID, 32)
net.WriteUInt(total, 32)
net.Send(inventory:GetReceivers())
ix.log.Add(client, "storageMoneyGive", entity, amount, total)
client.ixStorageMoneyTimer = CurTime() + 0.5
end)
else
net.Receive("ixStorageOpen", function()
if (IsValid(ix.gui.menu)) then
net.Start("ixStorageClose")
net.SendToServer()
return
end
local id = net.ReadUInt(32)
local entity = net.ReadEntity()
local name = net.ReadString()
local data = net.ReadTable()
local inventory = ix.item.inventories[id]
if (IsValid(entity) and inventory and inventory.slots) then
local localInventory = LocalPlayer():GetCharacter():GetInventory()
local panel = vgui.Create("ixStorageView")
if (localInventory) then
panel:SetLocalInventory(localInventory)
end
panel:SetStorageID(id)
panel:SetStorageTitle(name)
panel:SetStorageInventory(inventory)
if (data.money) then
if (localInventory) then
panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney())
end
panel:SetStorageMoney(data.money)
end
end
end)
net.Receive("ixStorageExpired", function()
if (IsValid(ix.gui.openedStorage)) then
ix.gui.openedStorage:Remove()
end
local id = net.ReadUInt(32)
if (id != 0) then
ix.item.inventories[id] = nil
end
end)
net.Receive("ixStorageMoneyUpdate", function()
local storageID = net.ReadUInt(32)
local amount = net.ReadUInt(32)
local panel = ix.gui.openedStorage
if (!IsValid(panel) or panel:GetStorageID() != storageID) then
return
end
panel:SetStorageMoney(amount)
panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney())
end)
end
================================================
FILE: gamemode/core/libs/sv_database.lua
================================================
ix.db = ix.db or {
schema = {},
schemaQueue = {},
type = {
-- TODO: more specific types, lengths, and defaults
-- i.e INT(11) UNSIGNED, SMALLINT(4), LONGTEXT, VARCHAR(350), NOT NULL, DEFAULT NULL, etc
[ix.type.string] = "VARCHAR(255)",
[ix.type.text] = "TEXT",
[ix.type.number] = "INT(11)",
[ix.type.steamid] = "VARCHAR(20)",
[ix.type.bool] = "TINYINT(1)"
}
}
ix.db.config = ix.config.server.database or {}
function ix.db.Connect()
ix.db.config.adapter = ix.db.config.adapter or "sqlite"
local dbmodule = ix.db.config.adapter
local hostname = ix.db.config.hostname
local username = ix.db.config.username
local password = ix.db.config.password
local database = ix.db.config.database
local port = ix.db.config.port
mysql:SetModule(dbmodule)
mysql:Connect(hostname, username, password, database, port)
end
function ix.db.AddToSchema(schemaType, field, fieldType)
if (!ix.db.type[fieldType]) then
error(string.format("attempted to add field in schema with invalid type '%s'", fieldType))
return
end
if (!mysql:IsConnected() or !ix.db.schema[schemaType]) then
ix.db.schemaQueue[#ix.db.schemaQueue + 1] = {schemaType, field, fieldType}
return
end
ix.db.InsertSchema(schemaType, field, fieldType)
end
-- this is only ever used internally
function ix.db.InsertSchema(schemaType, field, fieldType)
local schema = ix.db.schema[schemaType]
if (!schema) then
error(string.format("attempted to insert into schema with invalid schema type '%s'", schemaType))
return
end
if (!schema[field]) then
schema[field] = true
local query = mysql:Update("ix_schema")
query:Update("columns", util.TableToJSON(schema))
query:Where("table", schemaType)
query:Execute()
query = mysql:Alter(schemaType)
query:Add(field, ix.db.type[fieldType])
query:Execute()
end
end
function ix.db.LoadTables()
local query
query = mysql:Create("ix_schema")
query:Create("table", "VARCHAR(64) NOT NULL")
query:Create("columns", "TEXT NOT NULL")
query:PrimaryKey("table")
query:Execute()
-- table structure will be populated with more fields when vars
-- are registered using ix.char.RegisterVar
query = mysql:Create("ix_characters")
query:Create("id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
query:PrimaryKey("id")
query:Execute()
query = mysql:Create("ix_inventories")
query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
query:Create("character_id", "INT(11) UNSIGNED NOT NULL")
query:Create("inventory_type", "VARCHAR(150) DEFAULT NULL")
query:PrimaryKey("inventory_id")
query:Execute()
query = mysql:Create("ix_items")
query:Create("item_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL")
query:Create("unique_id", "VARCHAR(60) NOT NULL")
query:Create("character_id", "INT(11) UNSIGNED DEFAULT NULL")
query:Create("player_id", "VARCHAR(20) DEFAULT NULL")
query:Create("data", "TEXT DEFAULT NULL")
query:Create("x", "SMALLINT(4) NOT NULL")
query:Create("y", "SMALLINT(4) NOT NULL")
query:PrimaryKey("item_id")
query:Execute()
query = mysql:Create("ix_players")
query:Create("steamid", "VARCHAR(20) NOT NULL")
query:Create("steam_name", "VARCHAR(32) NOT NULL")
query:Create("play_time", "INT(11) UNSIGNED DEFAULT NULL")
query:Create("address", "VARCHAR(15) DEFAULT NULL")
query:Create("last_join_time", "INT(11) UNSIGNED DEFAULT NULL")
query:Create("data", "TEXT")
query:PrimaryKey("steamid")
query:Execute()
-- populate schema table if rows don't exist
query = mysql:InsertIgnore("ix_schema")
query:Insert("table", "ix_characters")
query:Insert("columns", util.TableToJSON({}))
query:Execute()
-- load schema from database
query = mysql:Select("ix_schema")
query:Callback(function(result)
if (!istable(result)) then
return
end
for _, v in pairs(result) do
ix.db.schema[v.table] = util.JSONToTable(v.columns)
end
-- update schema if needed
for i = 1, #ix.db.schemaQueue do
local entry = ix.db.schemaQueue[i]
ix.db.InsertSchema(entry[1], entry[2], entry[3])
end
end)
query:Execute()
end
function ix.db.WipeTables(callback)
local query
query = mysql:Drop("ix_schema")
query:Execute()
query = mysql:Drop("ix_characters")
query:Execute()
query = mysql:Drop("ix_inventories")
query:Execute()
query = mysql:Drop("ix_items")
query:Execute()
query = mysql:Drop("ix_players")
query:Callback(callback)
query:Execute()
end
hook.Add("InitPostEntity", "ixDatabaseConnect", function()
-- Connect to the database using SQLite, mysqoo, or tmysql4.
ix.db.Connect()
end)
local resetCalled = 0
concommand.Add("ix_wipedb", function(client, cmd, arguments)
-- can only be ran through the server's console
if (!IsValid(client)) then
if (resetCalled < RealTime()) then
resetCalled = RealTime() + 3
MsgC(Color(255, 0, 0),
"[Helix] WIPING THE DATABASE WILL PERMENANTLY REMOVE ALL PLAYER, CHARACTER, ITEM, AND INVENTORY DATA.\n")
MsgC(Color(255, 0, 0), "[Helix] THE SERVER WILL RESTART TO APPLY THESE CHANGES WHEN COMPLETED.\n")
MsgC(Color(255, 0, 0), "[Helix] TO CONFIRM DATABASE RESET, RUN 'ix_wipedb' AGAIN WITHIN 3 SECONDS.\n")
else
resetCalled = 0
MsgC(Color(255, 0, 0), "[Helix] DATABASE WIPE IN PROGRESS...\n")
hook.Run("OnWipeTables")
ix.db.WipeTables(function()
MsgC(Color(255, 255, 0), "[Helix] DATABASE WIPE COMPLETED!\n")
RunConsoleCommand("changelevel", game.GetMap())
end)
end
end
end)
================================================
FILE: gamemode/core/libs/sv_networking.lua
================================================
-- @module ix.net
local entityMeta = FindMetaTable("Entity")
local playerMeta = FindMetaTable("Player")
ix.net = ix.net or {}
ix.net.list = ix.net.list or {}
ix.net.locals = ix.net.locals or {}
ix.net.globals = ix.net.globals or {}
util.AddNetworkString("ixGlobalVarSet")
util.AddNetworkString("ixLocalVarSet")
util.AddNetworkString("ixNetVarSet")
util.AddNetworkString("ixNetVarDelete")
-- Check if there is an attempt to send a function. Can't send those.
local function CheckBadType(name, object)
if (isfunction(object)) then
ErrorNoHalt("Net var '" .. name .. "' contains a bad object type!")
return true
elseif (istable(object)) then
for k, v in pairs(object) do
-- Check both the key and the value for tables, and has recursion.
if (CheckBadType(name, k) or CheckBadType(name, v)) then
return true
end
end
end
end
function GetNetVar(key, default) -- luacheck: globals GetNetVar
local value = ix.net.globals[key]
return value != nil and value or default
end
function SetNetVar(key, value, receiver) -- luacheck: globals SetNetVar
if (CheckBadType(key, value)) then return end
if (GetNetVar(key) == value and !istable(value)) then return end
ix.net.globals[key] = value
net.Start("ixGlobalVarSet")
net.WriteString(key)
net.WriteType(value)
if (receiver == nil) then
net.Broadcast()
else
net.Send(receiver)
end
end
--- Player networked variable functions
-- @classmod Player
--- Synchronizes networked variables to the client.
-- @realm server
-- @internal
function playerMeta:SyncVars()
for k, v in pairs(ix.net.globals) do
net.Start("ixGlobalVarSet")
net.WriteString(k)
net.WriteType(v)
net.Send(self)
end
for k, v in pairs(ix.net.locals[self] or {}) do
net.Start("ixLocalVarSet")
net.WriteString(k)
net.WriteType(v)
net.Send(self)
end
for entity, data in pairs(ix.net.list) do
if (IsValid(entity)) then
local index = entity:EntIndex()
for k, v in pairs(data) do
net.Start("ixNetVarSet")
net.WriteUInt(index, 16)
net.WriteString(k)
net.WriteType(v)
net.Send(self)
end
end
end
end
--- Retrieves a local networked variable. If it is not set, it'll return the default that you've specified.
-- Locally networked variables can only be retrieved from the owning player when used from the client.
-- @realm shared
-- @string key Identifier of the local variable
-- @param default Default value to return if the local variable is not set
-- @return Value associated with the key, or the default that was given if it doesn't exist
-- @usage print(client:GetLocalVar("secret"))
-- > 12345678
-- @see SetLocalVar
function playerMeta:GetLocalVar(key, default)
if (ix.net.locals[self] and ix.net.locals[self][key] != nil) then
return ix.net.locals[self][key]
end
return default
end
--- Sets the value of a local networked variable.
-- @realm server
-- @string key Identifier of the local variable
-- @param value New value to assign to the local variable
-- @usage client:SetLocalVar("secret", 12345678)
-- @see GetLocalVar
function playerMeta:SetLocalVar(key, value)
if (CheckBadType(key, value)) then return end
ix.net.locals[self] = ix.net.locals[self] or {}
ix.net.locals[self][key] = value
net.Start("ixLocalVarSet")
net.WriteString(key)
net.WriteType(value)
net.Send(self)
end
--- Entity networked variable functions
-- @classmod Entity
--- Retrieves a networked variable. If it is not set, it'll return the default that you've specified.
-- @realm shared
-- @string key Identifier of the networked variable
-- @param default Default value to return if the networked variable is not set
-- @return Value associated with the key, or the default that was given if it doesn't exist
-- @usage print(client:GetNetVar("example"))
-- > Hello World!
-- @see SetNetVar
function entityMeta:GetNetVar(key, default)
if (ix.net.list[self] and ix.net.list[self][key] != nil) then
return ix.net.list[self][key]
end
return default
end
--- Sets the value of a networked variable.
-- @realm server
-- @string key Identifier of the networked variable
-- @param value New value to assign to the networked variable
-- @tab[opt=nil] receiver The players to send the networked variable to
-- @usage client:SetNetVar("example", "Hello World!")
-- @see GetNetVar
function entityMeta:SetNetVar(key, value, receiver)
if (CheckBadType(key, value)) then return end
ix.net.list[self] = ix.net.list[self] or {}
if (ix.net.list[self][key] != value) then
ix.net.list[self][key] = value
end
self:SendNetVar(key, receiver)
end
--- Sends a networked variable.
-- @realm server
-- @internal
-- @string key Identifier of the networked variable
-- @tab[opt=nil] receiver The players to send the networked variable to
function entityMeta:SendNetVar(key, receiver)
net.Start("ixNetVarSet")
net.WriteUInt(self:EntIndex(), 16)
net.WriteString(key)
net.WriteType(ix.net.list[self] and ix.net.list[self][key])
if (receiver == nil) then
net.Broadcast()
else
net.Send(receiver)
end
end
--- Clears all of the networked variables.
-- @realm server
-- @internal
-- @tab[opt=nil] receiver The players to clear the networked variable for
function entityMeta:ClearNetVars(receiver)
ix.net.list[self] = nil
ix.net.locals[self] = nil
net.Start("ixNetVarDelete")
net.WriteUInt(self:EntIndex(), 16)
if (receiver == nil) then
net.Broadcast()
else
net.Send(receiver)
end
end
================================================
FILE: gamemode/core/libs/sv_player.lua
================================================
local playerMeta = FindMetaTable("Player")
-- Player data (outside of characters) handling.
do
util.AddNetworkString("ixData")
util.AddNetworkString("ixDataSync")
function playerMeta:LoadData(callback)
local name = self:SteamName()
local steamID64 = self:SteamID64()
local timestamp = math.floor(os.time())
local ip = self:IPAddress():match("%d+%.%d+%.%d+%.%d+")
local query = mysql:Select("ix_players")
query:Select("data")
query:Select("play_time")
query:Where("steamid", steamID64)
query:Callback(function(result)
if (IsValid(self) and istable(result) and #result > 0 and result[1].data) then
local updateQuery = mysql:Update("ix_players")
updateQuery:Update("last_join_time", timestamp)
updateQuery:Update("address", ip)
updateQuery:Where("steamid", steamID64)
updateQuery:Execute()
self.ixPlayTime = tonumber(result[1].play_time) or 0
self.ixData = util.JSONToTable(result[1].data)
if (callback) then
callback(self.ixData)
end
else
local insertQuery = mysql:Insert("ix_players")
insertQuery:Insert("steamid", steamID64)
insertQuery:Insert("steam_name", name)
insertQuery:Insert("play_time", 0)
insertQuery:Insert("address", ip)
insertQuery:Insert("last_join_time", timestamp)
insertQuery:Insert("data", util.TableToJSON({}))
insertQuery:Execute()
if (callback) then
callback({})
end
end
end)
query:Execute()
end
function playerMeta:SaveData()
if (self:IsBot()) then return end
local name = self:SteamName()
local steamID64 = self:SteamID64()
local query = mysql:Update("ix_players")
query:Update("steam_name", name)
query:Update("play_time", math.floor((self.ixPlayTime or 0) + (RealTime() - (self.ixJoinTime or RealTime() - 1))))
query:Update("data", util.TableToJSON(self.ixData))
query:Where("steamid", steamID64)
query:Execute()
end
function playerMeta:SetData(key, value, bNoNetworking)
self.ixData = self.ixData or {}
self.ixData[key] = value
if (!bNoNetworking) then
net.Start("ixData")
net.WriteString(key)
net.WriteType(value)
net.Send(self)
end
end
end
-- Whitelisting information for the player.
do
function playerMeta:SetWhitelisted(faction, whitelisted)
if (!whitelisted) then
whitelisted = nil
end
local data = ix.faction.indices[faction]
if (data) then
local whitelists = self:GetData("whitelists", {})
whitelists[Schema.folder] = whitelists[Schema.folder] or {}
whitelists[Schema.folder][data.uniqueID] = whitelisted and true or nil
self:SetData("whitelists", whitelists)
self:SaveData()
return true
end
return false
end
end
do
playerMeta.ixGive = playerMeta.ixGive or playerMeta.Give
function playerMeta:Give(className, bNoAmmo)
local weapon
self.ixWeaponGive = true
weapon = self:ixGive(className, bNoAmmo)
self.ixWeaponGive = nil
return weapon
end
end
================================================
FILE: gamemode/core/libs/thirdparty/cl_ikon.lua
================================================
--[[
BLACK TEA ICON LIBRARY FOR NUTSCRIPT 1.1
The MIT License (MIT)
Copyright (c) 2017, Kyu Yeon Lee(Black Tea Za rebel1324)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so, subject
to the following conditions:
The above copyright notice and thispermission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
TL;DR: https://tldrlegal.com/license/mit-license
OK -
Commercial Use
Modify
Distribute
Sublicense
Private Use
NOT OK -
Hold Liable
MUST -
Include Copyright
Include License
]]--
--[[
Default Tables.
]]--
ikon = ikon or {}
ikon.cache = ikon.cache or {}
ikon.requestList = ikon.requestList or {}
ikon.dev = false
ikon.maxSize = 8 -- 8x8 (512^2) is max icon size.
IKON_BUSY = 1
IKON_PROCESSING = 0
IKON_SOMETHINGWRONG = -1
local schemaName = schemaName or (Schema and Schema.folder)
--[[
Initialize hooks and RT Screens.
returns nothing
]]--
function ikon:init()
if (self.dev) then
hook.Add("HUDPaint", "ikon_dev2", ikon.showResult)
else
hook.Remove("HUDPaint", "ikon_dev2")
end
--[[
Being good at gmod is knowing all of stinky hacks
- Black Tea (2017)
]]--
ikon.haloAdd = ikon.haloAdd or halo.Add
function halo.Add(...)
if (ikon.rendering != true) then
ikon.haloAdd(...)
end
end
ikon.haloRender = ikon.haloRender or halo.Render
function halo.Render(...)
if (ikon.rendering != true) then
ikon.haloRender(...)
end
end
file.CreateDir("helix/icons")
file.CreateDir("helix/icons/" .. schemaName)
end
--[[
IKON Library Essential Material/Texture Declare
]]--
local TEXTURE_FLAGS_CLAMP_S = 0x0004
local TEXTURE_FLAGS_CLAMP_T = 0x0008
ikon.max = ikon.maxSize * 64
ikon.RT = GetRenderTargetEx("ixIconRendered",
ikon.max,
ikon.max,
RT_SIZE_NO_CHANGE,
MATERIAL_RT_DEPTH_SHARED,
bit.bor(TEXTURE_FLAGS_CLAMP_S, TEXTURE_FLAGS_CLAMP_T),
CREATERENDERTARGETFLAGS_UNFILTERABLE_OK,
IMAGE_FORMAT_RGBA8888
)
local tex_effect = GetRenderTarget("ixIconRenderedOutline", ikon.max, ikon.max)
local mat_outline = CreateMaterial("ixIconRenderedTemp", "UnlitGeneric", {
["$basetexture"] = tex_effect:GetName(),
["$translucent"] = 1
})
local lightPositions = {
BOX_TOP = Color(255, 255, 255),
BOX_FRONT = Color(255, 255, 255),
}
function ikon:renderHook()
local entity = ikon.renderEntity
if (halo.RenderedEntity() == entity) then
return
end
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
local x, y = 0, 0
local tab
if (ikon.info) then
tab = {
origin = ikon.info.pos,
angles = ikon.info.ang,
fov = ikon.info.fov,
outline = ikon.info.outline,
outCol = ikon.info.outlineColor
}
if (!tab.origin and !tab.angles and !tab.fov) then
table.Merge(tab, PositionSpawnIcon(entity, entity:GetPos(), true))
end
else
tab = PositionSpawnIcon(entity, entity:GetPos(), true)
end
-- Taking MDave's Tip
xpcall(function()
render.OverrideAlphaWriteEnable(true, true) -- some playermodel eyeballs will not render without this
render.SetWriteDepthToDestAlpha(false)
render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD)
render.SuppressEngineLighting(true)
render.Clear(0, 0, 0, 0, true, true)
render.SetLightingOrigin(vector_origin)
render.ResetModelLighting(200 / 255, 200 / 255, 200 / 255)
render.SetColorModulation(1, 1, 1)
for i = 0, 6 do
local col = lightPositions[i]
if (col) then
render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255)
end
end
if (tab.outline) then
ix.util.ResetStencilValues()
render.SetStencilEnable(true)
render.SetStencilWriteMask(137) -- yeah random number to avoid confliction
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS)
render.SetStencilPassOperation(STENCILOPERATION_REPLACE)
render.SetStencilFailOperation(STENCILOPERATION_REPLACE)
end
--[[
Add more effects on the Models!
]]--
if (ikon.info and ikon.info.drawHook) then
ikon.info.drawHook(entity)
end
cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h)
render.SetBlend(1)
entity:DrawModel()
cam.End3D()
if (tab.outline) then
render.PushRenderTarget(tex_effect)
render.Clear(0, 0, 0, 0)
render.ClearDepth()
cam.Start2D()
cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h)
render.SetBlend(0)
entity:DrawModel()
render.SetStencilWriteMask(138) -- could you please?
render.SetStencilTestMask(1)
render.SetStencilReferenceValue(1)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL)
render.SetStencilPassOperation(STENCILOPERATION_KEEP)
render.SetStencilFailOperation(STENCILOPERATION_KEEP)
cam.Start2D()
surface.SetDrawColor(tab.outCol or color_white)
surface.DrawRect(0, 0, ScrW(), ScrH())
cam.End2D()
cam.End3D()
cam.End2D()
render.PopRenderTarget()
render.SetBlend(1)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NOTEQUAL)
--[[
Thanks for Noiwex
NxServ.eu
]]--
cam.Start2D()
surface.SetMaterial(mat_outline)
surface.DrawTexturedRectUV(-2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max)
surface.DrawTexturedRectUV(2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max)
surface.DrawTexturedRectUV(0, 2, w, h, 0, 0, w / ikon.max, h / ikon.max)
surface.DrawTexturedRectUV(0, -2, w, h, 0, 0, w / ikon.max, h / ikon.max)
cam.End2D()
render.SetStencilEnable(false)
end
render.SuppressEngineLighting(false)
render.SetWriteDepthToDestAlpha(true)
render.OverrideAlphaWriteEnable(false)
end, function(message)
print(message)
end)
end
function ikon:showResult()
local x, y = ScrW() / 2, ScrH() / 2
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
surface.SetDrawColor(255, 255, 255, 255)
surface.DrawOutlinedRect(x, 0, w, h)
surface.SetMaterial(mat_outline)
surface.DrawTexturedRect(x, 0, w, h)
end
--[[
Renders the Icon with given arguments.
returns nothing
]]--
function ikon:renderIcon(name, w, h, mdl, camInfo, updateCache)
if (#ikon.requestList > 0) then return IKON_BUSY end
if (ikon.requestList[name]) then return IKON_PROCESSING end
if (!w or !h or !mdl) then return IKON_SOMETHINGWRONG end
local capturedIcon
ikon.curWidth = w or 1
ikon.curHeight = h or 1
ikon.renderModel = mdl
if (camInfo) then
ikon.info = camInfo
end
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
local sw, sh = ScrW(), ScrH()
if (ikon.renderModel) then
if (!IsValid(ikon.renderEntity)) then
ikon.renderEntity = ClientsideModel(ikon.renderModel, RENDERGROUP_BOTH)
ikon.renderEntity:SetNoDraw(true)
end
end
ikon.renderEntity:SetModel(ikon.renderModel)
local bone = ikon.renderEntity:LookupBone("ValveBiped.Bip01_Head1")
if (bone) then
ikon.renderEntity:SetEyeTarget(ikon.renderEntity:GetBonePosition(bone) + ikon.renderEntity:GetForward() * 32)
end
local oldRT = render.GetRenderTarget()
render.PushRenderTarget(ikon.RT)
ikon.rendering = true
ikon:renderHook()
ikon.rendering = nil
capturedIcon = render.Capture({
format = "png",
alpha = true,
x = 0,
y = 0,
w = w,
h = h
})
file.Write("helix/icons/" .. schemaName .. "/" .. name .. ".png", capturedIcon)
ikon.info = nil
render.PopRenderTarget()
if (updateCache) then
local materialID = tostring(os.time())
file.Write(materialID .. ".png", capturedIcon)
timer.Simple(0, function()
local material = Material("../data/".. materialID ..".png")
ikon.cache[name] = material
file.Delete(materialID .. ".png")
end)
end
ikon.requestList[name] = nil
return true
end
--[[
Gets rendered icon with given unique name.
returns IMaterial
]]--
function ikon:GetIcon(name)
if (ikon.cache[name]) then
return ikon.cache[name] -- yeah return cache
end
if (file.Exists("helix/icons/" .. schemaName .. "/" .. name .. ".png", "DATA")) then
ikon.cache[name] = Material("../data/helix/icons/" .. schemaName .. "/".. name ..".png")
return ikon.cache[name] -- yeah return cache
else
return false -- retryd
end
end
concommand.Add("ix_flushicon", function()
local root = "helix/icons/" .. schemaName
for _, v in ipairs(file.Find(root .. "/*.png", "DATA")) do
file.Delete(root .. "/" .. v)
end
ikon.cache = {}
end)
hook.Add("InitializedSchema", "updatePath", function()
schemaName = Schema.folder
ikon:init()
end)
if (schemaName) then
ikon:init()
end
================================================
FILE: gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua
================================================
utf8_lc_uc = {
["a"] = "A",
["b"] = "B",
["c"] = "C",
["d"] = "D",
["e"] = "E",
["f"] = "F",
["g"] = "G",
["h"] = "H",
["i"] = "I",
["j"] = "J",
["k"] = "K",
["l"] = "L",
["m"] = "M",
["n"] = "N",
["o"] = "O",
["p"] = "P",
["q"] = "Q",
["r"] = "R",
["s"] = "S",
["t"] = "T",
["u"] = "U",
["v"] = "V",
["w"] = "W",
["x"] = "X",
["y"] = "Y",
["z"] = "Z",
["µ"] = "Μ",
["à"] = "À",
["á"] = "Á",
["â"] = "Â",
["ã"] = "Ã",
["ä"] = "Ä",
["å"] = "Å",
["æ"] = "Æ",
["ç"] = "Ç",
["è"] = "È",
["é"] = "É",
["ê"] = "Ê",
["ë"] = "Ë",
["ì"] = "Ì",
["í"] = "Í",
["î"] = "Î",
["ï"] = "Ï",
["ð"] = "Ð",
["ñ"] = "Ñ",
["ò"] = "Ò",
["ó"] = "Ó",
["ô"] = "Ô",
["õ"] = "Õ",
["ö"] = "Ö",
["ø"] = "Ø",
["ù"] = "Ù",
["ú"] = "Ú",
["û"] = "Û",
["ü"] = "Ü",
["ý"] = "Ý",
["þ"] = "Þ",
["ÿ"] = "Ÿ",
["ā"] = "Ā",
["ă"] = "Ă",
["ą"] = "Ą",
["ć"] = "Ć",
["ĉ"] = "Ĉ",
["ċ"] = "Ċ",
["č"] = "Č",
["ď"] = "Ď",
["đ"] = "Đ",
["ē"] = "Ē",
["ĕ"] = "Ĕ",
["ė"] = "Ė",
["ę"] = "Ę",
["ě"] = "Ě",
["ĝ"] = "Ĝ",
["ğ"] = "Ğ",
["ġ"] = "Ġ",
["ģ"] = "Ģ",
["ĥ"] = "Ĥ",
["ħ"] = "Ħ",
["ĩ"] = "Ĩ",
["ī"] = "Ī",
["ĭ"] = "Ĭ",
["į"] = "Į",
["ı"] = "I",
["ij"] = "IJ",
["ĵ"] = "Ĵ",
["ķ"] = "Ķ",
["ĺ"] = "Ĺ",
["ļ"] = "Ļ",
["ľ"] = "Ľ",
["ŀ"] = "Ŀ",
["ł"] = "Ł",
["ń"] = "Ń",
["ņ"] = "Ņ",
["ň"] = "Ň",
["ŋ"] = "Ŋ",
["ō"] = "Ō",
["ŏ"] = "Ŏ",
["ő"] = "Ő",
["œ"] = "Œ",
["ŕ"] = "Ŕ",
["ŗ"] = "Ŗ",
["ř"] = "Ř",
["ś"] = "Ś",
["ŝ"] = "Ŝ",
["ş"] = "Ş",
["š"] = "Š",
["ţ"] = "Ţ",
["ť"] = "Ť",
["ŧ"] = "Ŧ",
["ũ"] = "Ũ",
["ū"] = "Ū",
["ŭ"] = "Ŭ",
["ů"] = "Ů",
["ű"] = "Ű",
["ų"] = "Ų",
["ŵ"] = "Ŵ",
["ŷ"] = "Ŷ",
["ź"] = "Ź",
["ż"] = "Ż",
["ž"] = "Ž",
["ſ"] = "S",
["ƀ"] = "Ƀ",
["ƃ"] = "Ƃ",
["ƅ"] = "Ƅ",
["ƈ"] = "Ƈ",
["ƌ"] = "Ƌ",
["ƒ"] = "Ƒ",
["ƕ"] = "Ƕ",
["ƙ"] = "Ƙ",
["ƚ"] = "Ƚ",
["ƞ"] = "Ƞ",
["ơ"] = "Ơ",
["ƣ"] = "Ƣ",
["ƥ"] = "Ƥ",
["ƨ"] = "Ƨ",
["ƭ"] = "Ƭ",
["ư"] = "Ư",
["ƴ"] = "Ƴ",
["ƶ"] = "Ƶ",
["ƹ"] = "Ƹ",
["ƽ"] = "Ƽ",
["ƿ"] = "Ƿ",
["Dž"] = "DŽ",
["dž"] = "DŽ",
["Lj"] = "LJ",
["lj"] = "LJ",
["Nj"] = "NJ",
["nj"] = "NJ",
["ǎ"] = "Ǎ",
["ǐ"] = "Ǐ",
["ǒ"] = "Ǒ",
["ǔ"] = "Ǔ",
["ǖ"] = "Ǖ",
["ǘ"] = "Ǘ",
["ǚ"] = "Ǚ",
["ǜ"] = "Ǜ",
["ǝ"] = "Ǝ",
["ǟ"] = "Ǟ",
["ǡ"] = "Ǡ",
["ǣ"] = "Ǣ",
["ǥ"] = "Ǥ",
["ǧ"] = "Ǧ",
["ǩ"] = "Ǩ",
["ǫ"] = "Ǫ",
["ǭ"] = "Ǭ",
["ǯ"] = "Ǯ",
["Dz"] = "DZ",
["dz"] = "DZ",
["ǵ"] = "Ǵ",
["ǹ"] = "Ǹ",
["ǻ"] = "Ǻ",
["ǽ"] = "Ǽ",
["ǿ"] = "Ǿ",
["ȁ"] = "Ȁ",
["ȃ"] = "Ȃ",
["ȅ"] = "Ȅ",
["ȇ"] = "Ȇ",
["ȉ"] = "Ȉ",
["ȋ"] = "Ȋ",
["ȍ"] = "Ȍ",
["ȏ"] = "Ȏ",
["ȑ"] = "Ȑ",
["ȓ"] = "Ȓ",
["ȕ"] = "Ȕ",
["ȗ"] = "Ȗ",
["ș"] = "Ș",
["ț"] = "Ț",
["ȝ"] = "Ȝ",
["ȟ"] = "Ȟ",
["ȣ"] = "Ȣ",
["ȥ"] = "Ȥ",
["ȧ"] = "Ȧ",
["ȩ"] = "Ȩ",
["ȫ"] = "Ȫ",
["ȭ"] = "Ȭ",
["ȯ"] = "Ȯ",
["ȱ"] = "Ȱ",
["ȳ"] = "Ȳ",
["ȼ"] = "Ȼ",
["ɂ"] = "Ɂ",
["ɇ"] = "Ɇ",
["ɉ"] = "Ɉ",
["ɋ"] = "Ɋ",
["ɍ"] = "Ɍ",
["ɏ"] = "Ɏ",
["ɓ"] = "Ɓ",
["ɔ"] = "Ɔ",
["ɖ"] = "Ɖ",
["ɗ"] = "Ɗ",
["ə"] = "Ə",
["ɛ"] = "Ɛ",
["ɠ"] = "Ɠ",
["ɣ"] = "Ɣ",
["ɨ"] = "Ɨ",
["ɩ"] = "Ɩ",
["ɫ"] = "Ɫ",
["ɯ"] = "Ɯ",
["ɲ"] = "Ɲ",
["ɵ"] = "Ɵ",
["ɽ"] = "Ɽ",
["ʀ"] = "Ʀ",
["ʃ"] = "Ʃ",
["ʈ"] = "Ʈ",
["ʉ"] = "Ʉ",
["ʊ"] = "Ʊ",
["ʋ"] = "Ʋ",
["ʌ"] = "Ʌ",
["ʒ"] = "Ʒ",
["ͅ"] = "Ι",
["ͻ"] = "Ͻ",
["ͼ"] = "Ͼ",
["ͽ"] = "Ͽ",
["ά"] = "Ά",
["έ"] = "Έ",
["ή"] = "Ή",
["ί"] = "Ί",
["α"] = "Α",
["β"] = "Β",
["γ"] = "Γ",
["δ"] = "Δ",
["ε"] = "Ε",
["ζ"] = "Ζ",
["η"] = "Η",
["θ"] = "Θ",
["ι"] = "Ι",
["κ"] = "Κ",
["λ"] = "Λ",
["μ"] = "Μ",
["ν"] = "Ν",
["ξ"] = "Ξ",
["ο"] = "Ο",
["π"] = "Π",
["ρ"] = "Ρ",
["ς"] = "Σ",
["σ"] = "Σ",
["τ"] = "Τ",
["υ"] = "Υ",
["φ"] = "Φ",
["χ"] = "Χ",
["ψ"] = "Ψ",
["ω"] = "Ω",
["ϊ"] = "Ϊ",
["ϋ"] = "Ϋ",
["ό"] = "Ό",
["ύ"] = "Ύ",
["ώ"] = "Ώ",
["ϐ"] = "Β",
["ϑ"] = "Θ",
["ϕ"] = "Φ",
["ϖ"] = "Π",
["ϙ"] = "Ϙ",
["ϛ"] = "Ϛ",
["ϝ"] = "Ϝ",
["ϟ"] = "Ϟ",
["ϡ"] = "Ϡ",
["ϣ"] = "Ϣ",
["ϥ"] = "Ϥ",
["ϧ"] = "Ϧ",
["ϩ"] = "Ϩ",
["ϫ"] = "Ϫ",
["ϭ"] = "Ϭ",
["ϯ"] = "Ϯ",
["ϰ"] = "Κ",
["ϱ"] = "Ρ",
["ϲ"] = "Ϲ",
["ϵ"] = "Ε",
["ϸ"] = "Ϸ",
["ϻ"] = "Ϻ",
["а"] = "А",
["б"] = "Б",
["в"] = "В",
["г"] = "Г",
["д"] = "Д",
["е"] = "Е",
["ж"] = "Ж",
["з"] = "З",
["и"] = "И",
["й"] = "Й",
["к"] = "К",
["л"] = "Л",
["м"] = "М",
["н"] = "Н",
["о"] = "О",
["п"] = "П",
["р"] = "Р",
["с"] = "С",
["т"] = "Т",
["у"] = "У",
["ф"] = "Ф",
["х"] = "Х",
["ц"] = "Ц",
["ч"] = "Ч",
["ш"] = "Ш",
["щ"] = "Щ",
["ъ"] = "Ъ",
["ы"] = "Ы",
["ь"] = "Ь",
["э"] = "Э",
["ю"] = "Ю",
["я"] = "Я",
["ѐ"] = "Ѐ",
["ё"] = "Ё",
["ђ"] = "Ђ",
["ѓ"] = "Ѓ",
["є"] = "Є",
["ѕ"] = "Ѕ",
["і"] = "І",
["ї"] = "Ї",
["ј"] = "Ј",
["љ"] = "Љ",
["њ"] = "Њ",
["ћ"] = "Ћ",
["ќ"] = "Ќ",
["ѝ"] = "Ѝ",
["ў"] = "Ў",
["џ"] = "Џ",
["ѡ"] = "Ѡ",
["ѣ"] = "Ѣ",
["ѥ"] = "Ѥ",
["ѧ"] = "Ѧ",
["ѩ"] = "Ѩ",
["ѫ"] = "Ѫ",
["ѭ"] = "Ѭ",
["ѯ"] = "Ѯ",
["ѱ"] = "Ѱ",
["ѳ"] = "Ѳ",
["ѵ"] = "Ѵ",
["ѷ"] = "Ѷ",
["ѹ"] = "Ѹ",
["ѻ"] = "Ѻ",
["ѽ"] = "Ѽ",
["ѿ"] = "Ѿ",
["ҁ"] = "Ҁ",
["ҋ"] = "Ҋ",
["ҍ"] = "Ҍ",
["ҏ"] = "Ҏ",
["ґ"] = "Ґ",
["ғ"] = "Ғ",
["ҕ"] = "Ҕ",
["җ"] = "Җ",
["ҙ"] = "Ҙ",
["қ"] = "Қ",
["ҝ"] = "Ҝ",
["ҟ"] = "Ҟ",
["ҡ"] = "Ҡ",
["ң"] = "Ң",
["ҥ"] = "Ҥ",
["ҧ"] = "Ҧ",
["ҩ"] = "Ҩ",
["ҫ"] = "Ҫ",
["ҭ"] = "Ҭ",
["ү"] = "Ү",
["ұ"] = "Ұ",
["ҳ"] = "Ҳ",
["ҵ"] = "Ҵ",
["ҷ"] = "Ҷ",
["ҹ"] = "Ҹ",
["һ"] = "Һ",
["ҽ"] = "Ҽ",
["ҿ"] = "Ҿ",
["ӂ"] = "Ӂ",
["ӄ"] = "Ӄ",
["ӆ"] = "Ӆ",
["ӈ"] = "Ӈ",
["ӊ"] = "Ӊ",
["ӌ"] = "Ӌ",
["ӎ"] = "Ӎ",
["ӏ"] = "Ӏ",
["ӑ"] = "Ӑ",
["ӓ"] = "Ӓ",
["ӕ"] = "Ӕ",
["ӗ"] = "Ӗ",
["ә"] = "Ә",
["ӛ"] = "Ӛ",
["ӝ"] = "Ӝ",
["ӟ"] = "Ӟ",
["ӡ"] = "Ӡ",
["ӣ"] = "Ӣ",
["ӥ"] = "Ӥ",
["ӧ"] = "Ӧ",
["ө"] = "Ө",
["ӫ"] = "Ӫ",
["ӭ"] = "Ӭ",
["ӯ"] = "Ӯ",
["ӱ"] = "Ӱ",
["ӳ"] = "Ӳ",
["ӵ"] = "Ӵ",
["ӷ"] = "Ӷ",
["ӹ"] = "Ӹ",
["ӻ"] = "Ӻ",
["ӽ"] = "Ӽ",
["ӿ"] = "Ӿ",
["ԁ"] = "Ԁ",
["ԃ"] = "Ԃ",
["ԅ"] = "Ԅ",
["ԇ"] = "Ԇ",
["ԉ"] = "Ԉ",
["ԋ"] = "Ԋ",
["ԍ"] = "Ԍ",
["ԏ"] = "Ԏ",
["ԑ"] = "Ԑ",
["ԓ"] = "Ԓ",
["ա"] = "Ա",
["բ"] = "Բ",
["գ"] = "Գ",
["դ"] = "Դ",
["ե"] = "Ե",
["զ"] = "Զ",
["է"] = "Է",
["ը"] = "Ը",
["թ"] = "Թ",
["ժ"] = "Ժ",
["ի"] = "Ի",
["լ"] = "Լ",
["խ"] = "Խ",
["ծ"] = "Ծ",
["կ"] = "Կ",
["հ"] = "Հ",
["ձ"] = "Ձ",
["ղ"] = "Ղ",
["ճ"] = "Ճ",
["մ"] = "Մ",
["յ"] = "Յ",
["ն"] = "Ն",
["շ"] = "Շ",
["ո"] = "Ո",
["չ"] = "Չ",
["պ"] = "Պ",
["ջ"] = "Ջ",
["ռ"] = "Ռ",
["ս"] = "Ս",
["վ"] = "Վ",
["տ"] = "Տ",
["ր"] = "Ր",
["ց"] = "Ց",
["ւ"] = "Ւ",
["փ"] = "Փ",
["ք"] = "Ք",
["օ"] = "Օ",
["ֆ"] = "Ֆ",
["ᵽ"] = "Ᵽ",
["ḁ"] = "Ḁ",
["ḃ"] = "Ḃ",
["ḅ"] = "Ḅ",
["ḇ"] = "Ḇ",
["ḉ"] = "Ḉ",
["ḋ"] = "Ḋ",
["ḍ"] = "Ḍ",
["ḏ"] = "Ḏ",
["ḑ"] = "Ḑ",
["ḓ"] = "Ḓ",
["ḕ"] = "Ḕ",
["ḗ"] = "Ḗ",
["ḙ"] = "Ḙ",
["ḛ"] = "Ḛ",
["ḝ"] = "Ḝ",
["ḟ"] = "Ḟ",
["ḡ"] = "Ḡ",
["ḣ"] = "Ḣ",
["ḥ"] = "Ḥ",
["ḧ"] = "Ḧ",
["ḩ"] = "Ḩ",
["ḫ"] = "Ḫ",
["ḭ"] = "Ḭ",
["ḯ"] = "Ḯ",
["ḱ"] = "Ḱ",
["ḳ"] = "Ḳ",
["ḵ"] = "Ḵ",
["ḷ"] = "Ḷ",
["ḹ"] = "Ḹ",
["ḻ"] = "Ḻ",
["ḽ"] = "Ḽ",
["ḿ"] = "Ḿ",
["ṁ"] = "Ṁ",
["ṃ"] = "Ṃ",
["ṅ"] = "Ṅ",
["ṇ"] = "Ṇ",
["ṉ"] = "Ṉ",
["ṋ"] = "Ṋ",
["ṍ"] = "Ṍ",
["ṏ"] = "Ṏ",
["ṑ"] = "Ṑ",
["ṓ"] = "Ṓ",
["ṕ"] = "Ṕ",
["ṗ"] = "Ṗ",
["ṙ"] = "Ṙ",
["ṛ"] = "Ṛ",
["ṝ"] = "Ṝ",
["ṟ"] = "Ṟ",
["ṡ"] = "Ṡ",
["ṣ"] = "Ṣ",
["ṥ"] = "Ṥ",
["ṧ"] = "Ṧ",
["ṩ"] = "Ṩ",
["ṫ"] = "Ṫ",
["ṭ"] = "Ṭ",
["ṯ"] = "Ṯ",
["ṱ"] = "Ṱ",
["ṳ"] = "Ṳ",
["ṵ"] = "Ṵ",
["ṷ"] = "Ṷ",
["ṹ"] = "Ṹ",
["ṻ"] = "Ṻ",
["ṽ"] = "Ṽ",
["ṿ"] = "Ṿ",
["ẁ"] = "Ẁ",
["ẃ"] = "Ẃ",
["ẅ"] = "Ẅ",
["ẇ"] = "Ẇ",
["ẉ"] = "Ẉ",
["ẋ"] = "Ẋ",
["ẍ"] = "Ẍ",
["ẏ"] = "Ẏ",
["ẑ"] = "Ẑ",
["ẓ"] = "Ẓ",
["ẕ"] = "Ẕ",
["ẛ"] = "Ṡ",
["ạ"] = "Ạ",
["ả"] = "Ả",
["ấ"] = "Ấ",
["ầ"] = "Ầ",
["ẩ"] = "Ẩ",
["ẫ"] = "Ẫ",
["ậ"] = "Ậ",
["ắ"] = "Ắ",
["ằ"] = "Ằ",
["ẳ"] = "Ẳ",
["ẵ"] = "Ẵ",
["ặ"] = "Ặ",
["ẹ"] = "Ẹ",
["ẻ"] = "Ẻ",
["ẽ"] = "Ẽ",
["ế"] = "Ế",
["ề"] = "Ề",
["ể"] = "Ể",
["ễ"] = "Ễ",
["ệ"] = "Ệ",
["ỉ"] = "Ỉ",
["ị"] = "Ị",
["ọ"] = "Ọ",
["ỏ"] = "Ỏ",
["ố"] = "Ố",
["ồ"] = "Ồ",
["ổ"] = "Ổ",
["ỗ"] = "Ỗ",
["ộ"] = "Ộ",
["ớ"] = "Ớ",
["ờ"] = "Ờ",
["ở"] = "Ở",
["ỡ"] = "Ỡ",
["ợ"] = "Ợ",
["ụ"] = "Ụ",
["ủ"] = "Ủ",
["ứ"] = "Ứ",
["ừ"] = "Ừ",
["ử"] = "Ử",
["ữ"] = "Ữ",
["ự"] = "Ự",
["ỳ"] = "Ỳ",
["ỵ"] = "Ỵ",
["ỷ"] = "Ỷ",
["ỹ"] = "Ỹ",
["ἀ"] = "Ἀ",
["ἁ"] = "Ἁ",
["ἂ"] = "Ἂ",
["ἃ"] = "Ἃ",
["ἄ"] = "Ἄ",
["ἅ"] = "Ἅ",
["ἆ"] = "Ἆ",
["ἇ"] = "Ἇ",
["ἐ"] = "Ἐ",
["ἑ"] = "Ἑ",
["ἒ"] = "Ἒ",
["ἓ"] = "Ἓ",
["ἔ"] = "Ἔ",
["ἕ"] = "Ἕ",
["ἠ"] = "Ἠ",
["ἡ"] = "Ἡ",
["ἢ"] = "Ἢ",
["ἣ"] = "Ἣ",
["ἤ"] = "Ἤ",
["ἥ"] = "Ἥ",
["ἦ"] = "Ἦ",
["ἧ"] = "Ἧ",
["ἰ"] = "Ἰ",
["ἱ"] = "Ἱ",
["ἲ"] = "Ἲ",
["ἳ"] = "Ἳ",
["ἴ"] = "Ἴ",
["ἵ"] = "Ἵ",
["ἶ"] = "Ἶ",
["ἷ"] = "Ἷ",
["ὀ"] = "Ὀ",
["ὁ"] = "Ὁ",
["ὂ"] = "Ὂ",
["ὃ"] = "Ὃ",
["ὄ"] = "Ὄ",
["ὅ"] = "Ὅ",
["ὑ"] = "Ὑ",
["ὓ"] = "Ὓ",
["ὕ"] = "Ὕ",
["ὗ"] = "Ὗ",
["ὠ"] = "Ὠ",
["ὡ"] = "Ὡ",
["ὢ"] = "Ὢ",
["ὣ"] = "Ὣ",
["ὤ"] = "Ὤ",
["ὥ"] = "Ὥ",
["ὦ"] = "Ὦ",
["ὧ"] = "Ὧ",
["ὰ"] = "Ὰ",
["ά"] = "Ά",
["ὲ"] = "Ὲ",
["έ"] = "Έ",
["ὴ"] = "Ὴ",
["ή"] = "Ή",
["ὶ"] = "Ὶ",
["ί"] = "Ί",
["ὸ"] = "Ὸ",
["ό"] = "Ό",
["ὺ"] = "Ὺ",
["ύ"] = "Ύ",
["ὼ"] = "Ὼ",
["ώ"] = "Ώ",
["ᾀ"] = "ᾈ",
["ᾁ"] = "ᾉ",
["ᾂ"] = "ᾊ",
["ᾃ"] = "ᾋ",
["ᾄ"] = "ᾌ",
["ᾅ"] = "ᾍ",
["ᾆ"] = "ᾎ",
["ᾇ"] = "ᾏ",
["ᾐ"] = "ᾘ",
["ᾑ"] = "ᾙ",
["ᾒ"] = "ᾚ",
["ᾓ"] = "ᾛ",
["ᾔ"] = "ᾜ",
["ᾕ"] = "ᾝ",
["ᾖ"] = "ᾞ",
["ᾗ"] = "ᾟ",
["ᾠ"] = "ᾨ",
["ᾡ"] = "ᾩ",
["ᾢ"] = "ᾪ",
["ᾣ"] = "ᾫ",
["ᾤ"] = "ᾬ",
["ᾥ"] = "ᾭ",
["ᾦ"] = "ᾮ",
["ᾧ"] = "ᾯ",
["ᾰ"] = "Ᾰ",
["ᾱ"] = "Ᾱ",
["ᾳ"] = "ᾼ",
["ι"] = "Ι",
["ῃ"] = "ῌ",
["ῐ"] = "Ῐ",
["ῑ"] = "Ῑ",
["ῠ"] = "Ῠ",
["ῡ"] = "Ῡ",
["ῥ"] = "Ῥ",
["ῳ"] = "ῼ",
["ⅎ"] = "Ⅎ",
["ⅰ"] = "Ⅰ",
["ⅱ"] = "Ⅱ",
["ⅲ"] = "Ⅲ",
["ⅳ"] = "Ⅳ",
["ⅴ"] = "Ⅴ",
["ⅵ"] = "Ⅵ",
["ⅶ"] = "Ⅶ",
["ⅷ"] = "Ⅷ",
["ⅸ"] = "Ⅸ",
["ⅹ"] = "Ⅹ",
["ⅺ"] = "Ⅺ",
["ⅻ"] = "Ⅻ",
["ⅼ"] = "Ⅼ",
["ⅽ"] = "Ⅽ",
["ⅾ"] = "Ⅾ",
["ⅿ"] = "Ⅿ",
["ↄ"] = "Ↄ",
["ⓐ"] = "Ⓐ",
["ⓑ"] = "Ⓑ",
["ⓒ"] = "Ⓒ",
["ⓓ"] = "Ⓓ",
["ⓔ"] = "Ⓔ",
["ⓕ"] = "Ⓕ",
["ⓖ"] = "Ⓖ",
["ⓗ"] = "Ⓗ",
["ⓘ"] = "Ⓘ",
["ⓙ"] = "Ⓙ",
["ⓚ"] = "Ⓚ",
["ⓛ"] = "Ⓛ",
["ⓜ"] = "Ⓜ",
["ⓝ"] = "Ⓝ",
["ⓞ"] = "Ⓞ",
["ⓟ"] = "Ⓟ",
["ⓠ"] = "Ⓠ",
["ⓡ"] = "Ⓡ",
["ⓢ"] = "Ⓢ",
["ⓣ"] = "Ⓣ",
["ⓤ"] = "Ⓤ",
["ⓥ"] = "Ⓥ",
["ⓦ"] = "Ⓦ",
["ⓧ"] = "Ⓧ",
["ⓨ"] = "Ⓨ",
["ⓩ"] = "Ⓩ",
["ⰰ"] = "Ⰰ",
["ⰱ"] = "Ⰱ",
["ⰲ"] = "Ⰲ",
["ⰳ"] = "Ⰳ",
["ⰴ"] = "Ⰴ",
["ⰵ"] = "Ⰵ",
["ⰶ"] = "Ⰶ",
["ⰷ"] = "Ⰷ",
["ⰸ"] = "Ⰸ",
["ⰹ"] = "Ⰹ",
["ⰺ"] = "Ⰺ",
["ⰻ"] = "Ⰻ",
["ⰼ"] = "Ⰼ",
["ⰽ"] = "Ⰽ",
["ⰾ"] = "Ⰾ",
["ⰿ"] = "Ⰿ",
["ⱀ"] = "Ⱀ",
["ⱁ"] = "Ⱁ",
["ⱂ"] = "Ⱂ",
["ⱃ"] = "Ⱃ",
["ⱄ"] = "Ⱄ",
["ⱅ"] = "Ⱅ",
["ⱆ"] = "Ⱆ",
["ⱇ"] = "Ⱇ",
["ⱈ"] = "Ⱈ",
["ⱉ"] = "Ⱉ",
["ⱊ"] = "Ⱊ",
["ⱋ"] = "Ⱋ",
["ⱌ"] = "Ⱌ",
["ⱍ"] = "Ⱍ",
["ⱎ"] = "Ⱎ",
["ⱏ"] = "Ⱏ",
["ⱐ"] = "Ⱐ",
["ⱑ"] = "Ⱑ",
["ⱒ"] = "Ⱒ",
["ⱓ"] = "Ⱓ",
["ⱔ"] = "Ⱔ",
["ⱕ"] = "Ⱕ",
["ⱖ"] = "Ⱖ",
["ⱗ"] = "Ⱗ",
["ⱘ"] = "Ⱘ",
["ⱙ"] = "Ⱙ",
["ⱚ"] = "Ⱚ",
["ⱛ"] = "Ⱛ",
["ⱜ"] = "Ⱜ",
["ⱝ"] = "Ⱝ",
["ⱞ"] = "Ⱞ",
["ⱡ"] = "Ⱡ",
["ⱥ"] = "Ⱥ",
["ⱦ"] = "Ⱦ",
["ⱨ"] = "Ⱨ",
["ⱪ"] = "Ⱪ",
["ⱬ"] = "Ⱬ",
["ⱶ"] = "Ⱶ",
["ⲁ"] = "Ⲁ",
["ⲃ"] = "Ⲃ",
["ⲅ"] = "Ⲅ",
["ⲇ"] = "Ⲇ",
["ⲉ"] = "Ⲉ",
["ⲋ"] = "Ⲋ",
["ⲍ"] = "Ⲍ",
["ⲏ"] = "Ⲏ",
["ⲑ"] = "Ⲑ",
["ⲓ"] = "Ⲓ",
["ⲕ"] = "Ⲕ",
["ⲗ"] = "Ⲗ",
["ⲙ"] = "Ⲙ",
["ⲛ"] = "Ⲛ",
["ⲝ"] = "Ⲝ",
["ⲟ"] = "Ⲟ",
["ⲡ"] = "Ⲡ",
["ⲣ"] = "Ⲣ",
["ⲥ"] = "Ⲥ",
["ⲧ"] = "Ⲧ",
["ⲩ"] = "Ⲩ",
["ⲫ"] = "Ⲫ",
["ⲭ"] = "Ⲭ",
["ⲯ"] = "Ⲯ",
["ⲱ"] = "Ⲱ",
["ⲳ"] = "Ⲳ",
["ⲵ"] = "Ⲵ",
["ⲷ"] = "Ⲷ",
["ⲹ"] = "Ⲹ",
["ⲻ"] = "Ⲻ",
["ⲽ"] = "Ⲽ",
["ⲿ"] = "Ⲿ",
["ⳁ"] = "Ⳁ",
["ⳃ"] = "Ⳃ",
["ⳅ"] = "Ⳅ",
["ⳇ"] = "Ⳇ",
["ⳉ"] = "Ⳉ",
["ⳋ"] = "Ⳋ",
["ⳍ"] = "Ⳍ",
["ⳏ"] = "Ⳏ",
["ⳑ"] = "Ⳑ",
["ⳓ"] = "Ⳓ",
["ⳕ"] = "Ⳕ",
["ⳗ"] = "Ⳗ",
["ⳙ"] = "Ⳙ",
["ⳛ"] = "Ⳛ",
["ⳝ"] = "Ⳝ",
["ⳟ"] = "Ⳟ",
["ⳡ"] = "Ⳡ",
["ⳣ"] = "Ⳣ",
["ⴀ"] = "Ⴀ",
["ⴁ"] = "Ⴁ",
["ⴂ"] = "Ⴂ",
["ⴃ"] = "Ⴃ",
["ⴄ"] = "Ⴄ",
["ⴅ"] = "Ⴅ",
["ⴆ"] = "Ⴆ",
["ⴇ"] = "Ⴇ",
["ⴈ"] = "Ⴈ",
["ⴉ"] = "Ⴉ",
["ⴊ"] = "Ⴊ",
["ⴋ"] = "Ⴋ",
["ⴌ"] = "Ⴌ",
["ⴍ"] = "Ⴍ",
["ⴎ"] = "Ⴎ",
["ⴏ"] = "Ⴏ",
["ⴐ"] = "Ⴐ",
["ⴑ"] = "Ⴑ",
["ⴒ"] = "Ⴒ",
["ⴓ"] = "Ⴓ",
["ⴔ"] = "Ⴔ",
["ⴕ"] = "Ⴕ",
["ⴖ"] = "Ⴖ",
["ⴗ"] = "Ⴗ",
["ⴘ"] = "Ⴘ",
["ⴙ"] = "Ⴙ",
["ⴚ"] = "Ⴚ",
["ⴛ"] = "Ⴛ",
["ⴜ"] = "Ⴜ",
["ⴝ"] = "Ⴝ",
["ⴞ"] = "Ⴞ",
["ⴟ"] = "Ⴟ",
["ⴠ"] = "Ⴠ",
["ⴡ"] = "Ⴡ",
["ⴢ"] = "Ⴢ",
["ⴣ"] = "Ⴣ",
["ⴤ"] = "Ⴤ",
["ⴥ"] = "Ⴥ",
["a"] = "A",
["b"] = "B",
["c"] = "C",
["d"] = "D",
["e"] = "E",
["f"] = "F",
["g"] = "G",
["h"] = "H",
["i"] = "I",
["j"] = "J",
["k"] = "K",
["l"] = "L",
["m"] = "M",
["n"] = "N",
["o"] = "O",
["p"] = "P",
["q"] = "Q",
["r"] = "R",
["s"] = "S",
["t"] = "T",
["u"] = "U",
["v"] = "V",
["w"] = "W",
["x"] = "X",
["y"] = "Y",
["z"] = "Z",
["𐐨"] = "𐐀",
["𐐩"] = "𐐁",
["𐐪"] = "𐐂",
["𐐫"] = "𐐃",
["𐐬"] = "𐐄",
["𐐭"] = "𐐅",
["𐐮"] = "𐐆",
["𐐯"] = "𐐇",
["𐐰"] = "𐐈",
["𐐱"] = "𐐉",
["𐐲"] = "𐐊",
["𐐳"] = "𐐋",
["𐐴"] = "𐐌",
["𐐵"] = "𐐍",
["𐐶"] = "𐐎",
["𐐷"] = "𐐏",
["𐐸"] = "𐐐",
["𐐹"] = "𐐑",
["𐐺"] = "𐐒",
["𐐻"] = "𐐓",
["𐐼"] = "𐐔",
["𐐽"] = "𐐕",
["𐐾"] = "𐐖",
["𐐿"] = "𐐗",
["𐑀"] = "𐐘",
["𐑁"] = "𐐙",
["𐑂"] = "𐐚",
["𐑃"] = "𐐛",
["𐑄"] = "𐐜",
["𐑅"] = "𐐝",
["𐑆"] = "𐐞",
["𐑇"] = "𐐟",
["𐑈"] = "𐐠",
["𐑉"] = "𐐡",
["𐑊"] = "𐐢",
["𐑋"] = "𐐣",
["𐑌"] = "𐐤",
["𐑍"] = "𐐥",
["𐑎"] = "𐐦",
["𐑏"] = "𐐧",
}
utf8_uc_lc = {
["A"] = "a",
["B"] = "b",
["C"] = "c",
["D"] = "d",
["E"] = "e",
["F"] = "f",
["G"] = "g",
["H"] = "h",
["I"] = "i",
["J"] = "j",
["K"] = "k",
["L"] = "l",
["M"] = "m",
["N"] = "n",
["O"] = "o",
["P"] = "p",
["Q"] = "q",
["R"] = "r",
["S"] = "s",
["T"] = "t",
["U"] = "u",
["V"] = "v",
["W"] = "w",
["X"] = "x",
["Y"] = "y",
["Z"] = "z",
["À"] = "à",
["Á"] = "á",
["Â"] = "â",
["Ã"] = "ã",
["Ä"] = "ä",
["Å"] = "å",
["Æ"] = "æ",
["Ç"] = "ç",
["È"] = "è",
["É"] = "é",
["Ê"] = "ê",
["Ë"] = "ë",
["Ì"] = "ì",
["Í"] = "í",
["Î"] = "î",
["Ï"] = "ï",
["Ð"] = "ð",
["Ñ"] = "ñ",
["Ò"] = "ò",
["Ó"] = "ó",
["Ô"] = "ô",
["Õ"] = "õ",
["Ö"] = "ö",
["Ø"] = "ø",
["Ù"] = "ù",
["Ú"] = "ú",
["Û"] = "û",
["Ü"] = "ü",
["Ý"] = "ý",
["Þ"] = "þ",
["Ā"] = "ā",
["Ă"] = "ă",
["Ą"] = "ą",
["Ć"] = "ć",
["Ĉ"] = "ĉ",
["Ċ"] = "ċ",
["Č"] = "č",
["Ď"] = "ď",
["Đ"] = "đ",
["Ē"] = "ē",
["Ĕ"] = "ĕ",
["Ė"] = "ė",
["Ę"] = "ę",
["Ě"] = "ě",
["Ĝ"] = "ĝ",
["Ğ"] = "ğ",
["Ġ"] = "ġ",
["Ģ"] = "ģ",
["Ĥ"] = "ĥ",
["Ħ"] = "ħ",
["Ĩ"] = "ĩ",
["Ī"] = "ī",
["Ĭ"] = "ĭ",
["Į"] = "į",
["İ"] = "i",
["IJ"] = "ij",
["Ĵ"] = "ĵ",
["Ķ"] = "ķ",
["Ĺ"] = "ĺ",
["Ļ"] = "ļ",
["Ľ"] = "ľ",
["Ŀ"] = "ŀ",
["Ł"] = "ł",
["Ń"] = "ń",
["Ņ"] = "ņ",
["Ň"] = "ň",
["Ŋ"] = "ŋ",
["Ō"] = "ō",
["Ŏ"] = "ŏ",
["Ő"] = "ő",
["Œ"] = "œ",
["Ŕ"] = "ŕ",
["Ŗ"] = "ŗ",
["Ř"] = "ř",
["Ś"] = "ś",
["Ŝ"] = "ŝ",
["Ş"] = "ş",
["Š"] = "š",
["Ţ"] = "ţ",
["Ť"] = "ť",
["Ŧ"] = "ŧ",
["Ũ"] = "ũ",
["Ū"] = "ū",
["Ŭ"] = "ŭ",
["Ů"] = "ů",
["Ű"] = "ű",
["Ų"] = "ų",
["Ŵ"] = "ŵ",
["Ŷ"] = "ŷ",
["Ÿ"] = "ÿ",
["Ź"] = "ź",
["Ż"] = "ż",
["Ž"] = "ž",
["Ɓ"] = "ɓ",
["Ƃ"] = "ƃ",
["Ƅ"] = "ƅ",
["Ɔ"] = "ɔ",
["Ƈ"] = "ƈ",
["Ɖ"] = "ɖ",
["Ɗ"] = "ɗ",
["Ƌ"] = "ƌ",
["Ǝ"] = "ǝ",
["Ə"] = "ə",
["Ɛ"] = "ɛ",
["Ƒ"] = "ƒ",
["Ɠ"] = "ɠ",
["Ɣ"] = "ɣ",
["Ɩ"] = "ɩ",
["Ɨ"] = "ɨ",
["Ƙ"] = "ƙ",
["Ɯ"] = "ɯ",
["Ɲ"] = "ɲ",
["Ɵ"] = "ɵ",
["Ơ"] = "ơ",
["Ƣ"] = "ƣ",
["Ƥ"] = "ƥ",
["Ʀ"] = "ʀ",
["Ƨ"] = "ƨ",
["Ʃ"] = "ʃ",
["Ƭ"] = "ƭ",
["Ʈ"] = "ʈ",
["Ư"] = "ư",
["Ʊ"] = "ʊ",
["Ʋ"] = "ʋ",
["Ƴ"] = "ƴ",
["Ƶ"] = "ƶ",
["Ʒ"] = "ʒ",
["Ƹ"] = "ƹ",
["Ƽ"] = "ƽ",
["DŽ"] = "dž",
["Dž"] = "dž",
["LJ"] = "lj",
["Lj"] = "lj",
["NJ"] = "nj",
["Nj"] = "nj",
["Ǎ"] = "ǎ",
["Ǐ"] = "ǐ",
["Ǒ"] = "ǒ",
["Ǔ"] = "ǔ",
["Ǖ"] = "ǖ",
["Ǘ"] = "ǘ",
["Ǚ"] = "ǚ",
["Ǜ"] = "ǜ",
["Ǟ"] = "ǟ",
["Ǡ"] = "ǡ",
["Ǣ"] = "ǣ",
["Ǥ"] = "ǥ",
["Ǧ"] = "ǧ",
["Ǩ"] = "ǩ",
["Ǫ"] = "ǫ",
["Ǭ"] = "ǭ",
["Ǯ"] = "ǯ",
["DZ"] = "dz",
["Dz"] = "dz",
["Ǵ"] = "ǵ",
["Ƕ"] = "ƕ",
["Ƿ"] = "ƿ",
["Ǹ"] = "ǹ",
["Ǻ"] = "ǻ",
["Ǽ"] = "ǽ",
["Ǿ"] = "ǿ",
["Ȁ"] = "ȁ",
["Ȃ"] = "ȃ",
["Ȅ"] = "ȅ",
["Ȇ"] = "ȇ",
["Ȉ"] = "ȉ",
["Ȋ"] = "ȋ",
["Ȍ"] = "ȍ",
["Ȏ"] = "ȏ",
["Ȑ"] = "ȑ",
["Ȓ"] = "ȓ",
["Ȕ"] = "ȕ",
["Ȗ"] = "ȗ",
["Ș"] = "ș",
["Ț"] = "ț",
["Ȝ"] = "ȝ",
["Ȟ"] = "ȟ",
["Ƞ"] = "ƞ",
["Ȣ"] = "ȣ",
["Ȥ"] = "ȥ",
["Ȧ"] = "ȧ",
["Ȩ"] = "ȩ",
["Ȫ"] = "ȫ",
["Ȭ"] = "ȭ",
["Ȯ"] = "ȯ",
["Ȱ"] = "ȱ",
["Ȳ"] = "ȳ",
["Ⱥ"] = "ⱥ",
["Ȼ"] = "ȼ",
["Ƚ"] = "ƚ",
["Ⱦ"] = "ⱦ",
["Ɂ"] = "ɂ",
["Ƀ"] = "ƀ",
["Ʉ"] = "ʉ",
["Ʌ"] = "ʌ",
["Ɇ"] = "ɇ",
["Ɉ"] = "ɉ",
["Ɋ"] = "ɋ",
["Ɍ"] = "ɍ",
["Ɏ"] = "ɏ",
["Ά"] = "ά",
["Έ"] = "έ",
["Ή"] = "ή",
["Ί"] = "ί",
["Ό"] = "ό",
["Ύ"] = "ύ",
["Ώ"] = "ώ",
["Α"] = "α",
["Β"] = "β",
["Γ"] = "γ",
["Δ"] = "δ",
["Ε"] = "ε",
["Ζ"] = "ζ",
["Η"] = "η",
["Θ"] = "θ",
["Ι"] = "ι",
["Κ"] = "κ",
["Λ"] = "λ",
["Μ"] = "μ",
["Ν"] = "ν",
["Ξ"] = "ξ",
["Ο"] = "ο",
["Π"] = "π",
["Ρ"] = "ρ",
["Σ"] = "σ",
["Τ"] = "τ",
["Υ"] = "υ",
["Φ"] = "φ",
["Χ"] = "χ",
["Ψ"] = "ψ",
["Ω"] = "ω",
["Ϊ"] = "ϊ",
["Ϋ"] = "ϋ",
["Ϙ"] = "ϙ",
["Ϛ"] = "ϛ",
["Ϝ"] = "ϝ",
["Ϟ"] = "ϟ",
["Ϡ"] = "ϡ",
["Ϣ"] = "ϣ",
["Ϥ"] = "ϥ",
["Ϧ"] = "ϧ",
["Ϩ"] = "ϩ",
["Ϫ"] = "ϫ",
["Ϭ"] = "ϭ",
["Ϯ"] = "ϯ",
["ϴ"] = "θ",
["Ϸ"] = "ϸ",
["Ϲ"] = "ϲ",
["Ϻ"] = "ϻ",
["Ͻ"] = "ͻ",
["Ͼ"] = "ͼ",
["Ͽ"] = "ͽ",
["Ѐ"] = "ѐ",
["Ё"] = "ё",
["Ђ"] = "ђ",
["Ѓ"] = "ѓ",
["Є"] = "є",
["Ѕ"] = "ѕ",
["І"] = "і",
["Ї"] = "ї",
["Ј"] = "ј",
["Љ"] = "љ",
["Њ"] = "њ",
["Ћ"] = "ћ",
["Ќ"] = "ќ",
["Ѝ"] = "ѝ",
["Ў"] = "ў",
["Џ"] = "џ",
["А"] = "а",
["Б"] = "б",
["В"] = "в",
["Г"] = "г",
["Д"] = "д",
["Е"] = "е",
["Ж"] = "ж",
["З"] = "з",
["И"] = "и",
["Й"] = "й",
["К"] = "к",
["Л"] = "л",
["М"] = "м",
["Н"] = "н",
["О"] = "о",
["П"] = "п",
["Р"] = "р",
["С"] = "с",
["Т"] = "т",
["У"] = "у",
["Ф"] = "ф",
["Х"] = "х",
["Ц"] = "ц",
["Ч"] = "ч",
["Ш"] = "ш",
["Щ"] = "щ",
["Ъ"] = "ъ",
["Ы"] = "ы",
["Ь"] = "ь",
["Э"] = "э",
["Ю"] = "ю",
["Я"] = "я",
["Ѡ"] = "ѡ",
["Ѣ"] = "ѣ",
["Ѥ"] = "ѥ",
["Ѧ"] = "ѧ",
["Ѩ"] = "ѩ",
["Ѫ"] = "ѫ",
["Ѭ"] = "ѭ",
["Ѯ"] = "ѯ",
["Ѱ"] = "ѱ",
["Ѳ"] = "ѳ",
["Ѵ"] = "ѵ",
["Ѷ"] = "ѷ",
["Ѹ"] = "ѹ",
["Ѻ"] = "ѻ",
["Ѽ"] = "ѽ",
["Ѿ"] = "ѿ",
["Ҁ"] = "ҁ",
["Ҋ"] = "ҋ",
["Ҍ"] = "ҍ",
["Ҏ"] = "ҏ",
["Ґ"] = "ґ",
["Ғ"] = "ғ",
["Ҕ"] = "ҕ",
["Җ"] = "җ",
["Ҙ"] = "ҙ",
["Қ"] = "қ",
["Ҝ"] = "ҝ",
["Ҟ"] = "ҟ",
["Ҡ"] = "ҡ",
["Ң"] = "ң",
["Ҥ"] = "ҥ",
["Ҧ"] = "ҧ",
["Ҩ"] = "ҩ",
["Ҫ"] = "ҫ",
["Ҭ"] = "ҭ",
["Ү"] = "ү",
["Ұ"] = "ұ",
["Ҳ"] = "ҳ",
["Ҵ"] = "ҵ",
["Ҷ"] = "ҷ",
["Ҹ"] = "ҹ",
["Һ"] = "һ",
["Ҽ"] = "ҽ",
["Ҿ"] = "ҿ",
["Ӏ"] = "ӏ",
["Ӂ"] = "ӂ",
["Ӄ"] = "ӄ",
["Ӆ"] = "ӆ",
["Ӈ"] = "ӈ",
["Ӊ"] = "ӊ",
["Ӌ"] = "ӌ",
["Ӎ"] = "ӎ",
["Ӑ"] = "ӑ",
["Ӓ"] = "ӓ",
["Ӕ"] = "ӕ",
["Ӗ"] = "ӗ",
["Ә"] = "ә",
["Ӛ"] = "ӛ",
["Ӝ"] = "ӝ",
["Ӟ"] = "ӟ",
["Ӡ"] = "ӡ",
["Ӣ"] = "ӣ",
["Ӥ"] = "ӥ",
["Ӧ"] = "ӧ",
["Ө"] = "ө",
["Ӫ"] = "ӫ",
["Ӭ"] = "ӭ",
["Ӯ"] = "ӯ",
["Ӱ"] = "ӱ",
["Ӳ"] = "ӳ",
["Ӵ"] = "ӵ",
["Ӷ"] = "ӷ",
["Ӹ"] = "ӹ",
["Ӻ"] = "ӻ",
["Ӽ"] = "ӽ",
["Ӿ"] = "ӿ",
["Ԁ"] = "ԁ",
["Ԃ"] = "ԃ",
["Ԅ"] = "ԅ",
["Ԇ"] = "ԇ",
["Ԉ"] = "ԉ",
["Ԋ"] = "ԋ",
["Ԍ"] = "ԍ",
["Ԏ"] = "ԏ",
["Ԑ"] = "ԑ",
["Ԓ"] = "ԓ",
["Ա"] = "ա",
["Բ"] = "բ",
["Գ"] = "գ",
["Դ"] = "դ",
["Ե"] = "ե",
["Զ"] = "զ",
["Է"] = "է",
["Ը"] = "ը",
["Թ"] = "թ",
["Ժ"] = "ժ",
["Ի"] = "ի",
["Լ"] = "լ",
["Խ"] = "խ",
["Ծ"] = "ծ",
["Կ"] = "կ",
["Հ"] = "հ",
["Ձ"] = "ձ",
["Ղ"] = "ղ",
["Ճ"] = "ճ",
["Մ"] = "մ",
["Յ"] = "յ",
["Ն"] = "ն",
["Շ"] = "շ",
["Ո"] = "ո",
["Չ"] = "չ",
["Պ"] = "պ",
["Ջ"] = "ջ",
["Ռ"] = "ռ",
["Ս"] = "ս",
["Վ"] = "վ",
["Տ"] = "տ",
["Ր"] = "ր",
["Ց"] = "ց",
["Ւ"] = "ւ",
["Փ"] = "փ",
["Ք"] = "ք",
["Օ"] = "օ",
["Ֆ"] = "ֆ",
["Ⴀ"] = "ⴀ",
["Ⴁ"] = "ⴁ",
["Ⴂ"] = "ⴂ",
["Ⴃ"] = "ⴃ",
["Ⴄ"] = "ⴄ",
["Ⴅ"] = "ⴅ",
["Ⴆ"] = "ⴆ",
["Ⴇ"] = "ⴇ",
["Ⴈ"] = "ⴈ",
["Ⴉ"] = "ⴉ",
["Ⴊ"] = "ⴊ",
["Ⴋ"] = "ⴋ",
["Ⴌ"] = "ⴌ",
["Ⴍ"] = "ⴍ",
["Ⴎ"] = "ⴎ",
["Ⴏ"] = "ⴏ",
["Ⴐ"] = "ⴐ",
["Ⴑ"] = "ⴑ",
["Ⴒ"] = "ⴒ",
["Ⴓ"] = "ⴓ",
["Ⴔ"] = "ⴔ",
["Ⴕ"] = "ⴕ",
["Ⴖ"] = "ⴖ",
["Ⴗ"] = "ⴗ",
["Ⴘ"] = "ⴘ",
["Ⴙ"] = "ⴙ",
["Ⴚ"] = "ⴚ",
["Ⴛ"] = "ⴛ",
["Ⴜ"] = "ⴜ",
["Ⴝ"] = "ⴝ",
["Ⴞ"] = "ⴞ",
["Ⴟ"] = "ⴟ",
["Ⴠ"] = "ⴠ",
["Ⴡ"] = "ⴡ",
["Ⴢ"] = "ⴢ",
["Ⴣ"] = "ⴣ",
["Ⴤ"] = "ⴤ",
["Ⴥ"] = "ⴥ",
["Ḁ"] = "ḁ",
["Ḃ"] = "ḃ",
["Ḅ"] = "ḅ",
["Ḇ"] = "ḇ",
["Ḉ"] = "ḉ",
["Ḋ"] = "ḋ",
["Ḍ"] = "ḍ",
["Ḏ"] = "ḏ",
["Ḑ"] = "ḑ",
["Ḓ"] = "ḓ",
["Ḕ"] = "ḕ",
["Ḗ"] = "ḗ",
["Ḙ"] = "ḙ",
["Ḛ"] = "ḛ",
["Ḝ"] = "ḝ",
["Ḟ"] = "ḟ",
["Ḡ"] = "ḡ",
["Ḣ"] = "ḣ",
["Ḥ"] = "ḥ",
["Ḧ"] = "ḧ",
["Ḩ"] = "ḩ",
["Ḫ"] = "ḫ",
["Ḭ"] = "ḭ",
["Ḯ"] = "ḯ",
["Ḱ"] = "ḱ",
["Ḳ"] = "ḳ",
["Ḵ"] = "ḵ",
["Ḷ"] = "ḷ",
["Ḹ"] = "ḹ",
["Ḻ"] = "ḻ",
["Ḽ"] = "ḽ",
["Ḿ"] = "ḿ",
["Ṁ"] = "ṁ",
["Ṃ"] = "ṃ",
["Ṅ"] = "ṅ",
["Ṇ"] = "ṇ",
["Ṉ"] = "ṉ",
["Ṋ"] = "ṋ",
["Ṍ"] = "ṍ",
["Ṏ"] = "ṏ",
["Ṑ"] = "ṑ",
["Ṓ"] = "ṓ",
["Ṕ"] = "ṕ",
["Ṗ"] = "ṗ",
["Ṙ"] = "ṙ",
["Ṛ"] = "ṛ",
["Ṝ"] = "ṝ",
["Ṟ"] = "ṟ",
["Ṡ"] = "ṡ",
["Ṣ"] = "ṣ",
["Ṥ"] = "ṥ",
["Ṧ"] = "ṧ",
["Ṩ"] = "ṩ",
["Ṫ"] = "ṫ",
["Ṭ"] = "ṭ",
["Ṯ"] = "ṯ",
["Ṱ"] = "ṱ",
["Ṳ"] = "ṳ",
["Ṵ"] = "ṵ",
["Ṷ"] = "ṷ",
["Ṹ"] = "ṹ",
["Ṻ"] = "ṻ",
["Ṽ"] = "ṽ",
["Ṿ"] = "ṿ",
["Ẁ"] = "ẁ",
["Ẃ"] = "ẃ",
["Ẅ"] = "ẅ",
["Ẇ"] = "ẇ",
["Ẉ"] = "ẉ",
["Ẋ"] = "ẋ",
["Ẍ"] = "ẍ",
["Ẏ"] = "ẏ",
["Ẑ"] = "ẑ",
["Ẓ"] = "ẓ",
["Ẕ"] = "ẕ",
["Ạ"] = "ạ",
["Ả"] = "ả",
["Ấ"] = "ấ",
["Ầ"] = "ầ",
["Ẩ"] = "ẩ",
["Ẫ"] = "ẫ",
["Ậ"] = "ậ",
["Ắ"] = "ắ",
["Ằ"] = "ằ",
["Ẳ"] = "ẳ",
["Ẵ"] = "ẵ",
["Ặ"] = "ặ",
["Ẹ"] = "ẹ",
["Ẻ"] = "ẻ",
["Ẽ"] = "ẽ",
["Ế"] = "ế",
["Ề"] = "ề",
["Ể"] = "ể",
["Ễ"] = "ễ",
["Ệ"] = "ệ",
["Ỉ"] = "ỉ",
["Ị"] = "ị",
["Ọ"] = "ọ",
["Ỏ"] = "ỏ",
["Ố"] = "ố",
["Ồ"] = "ồ",
["Ổ"] = "ổ",
["Ỗ"] = "ỗ",
["Ộ"] = "ộ",
["Ớ"] = "ớ",
["Ờ"] = "ờ",
["Ở"] = "ở",
["Ỡ"] = "ỡ",
["Ợ"] = "ợ",
["Ụ"] = "ụ",
["Ủ"] = "ủ",
["Ứ"] = "ứ",
["Ừ"] = "ừ",
["Ử"] = "ử",
["Ữ"] = "ữ",
["Ự"] = "ự",
["Ỳ"] = "ỳ",
["Ỵ"] = "ỵ",
["Ỷ"] = "ỷ",
["Ỹ"] = "ỹ",
["Ἀ"] = "ἀ",
["Ἁ"] = "ἁ",
["Ἂ"] = "ἂ",
["Ἃ"] = "ἃ",
["Ἄ"] = "ἄ",
["Ἅ"] = "ἅ",
["Ἆ"] = "ἆ",
["Ἇ"] = "ἇ",
["Ἐ"] = "ἐ",
["Ἑ"] = "ἑ",
["Ἒ"] = "ἒ",
["Ἓ"] = "ἓ",
["Ἔ"] = "ἔ",
["Ἕ"] = "ἕ",
["Ἠ"] = "ἠ",
["Ἡ"] = "ἡ",
["Ἢ"] = "ἢ",
["Ἣ"] = "ἣ",
["Ἤ"] = "ἤ",
["Ἥ"] = "ἥ",
["Ἦ"] = "ἦ",
["Ἧ"] = "ἧ",
["Ἰ"] = "ἰ",
["Ἱ"] = "ἱ",
["Ἲ"] = "ἲ",
["Ἳ"] = "ἳ",
["Ἴ"] = "ἴ",
["Ἵ"] = "ἵ",
["Ἶ"] = "ἶ",
["Ἷ"] = "ἷ",
["Ὀ"] = "ὀ",
["Ὁ"] = "ὁ",
["Ὂ"] = "ὂ",
["Ὃ"] = "ὃ",
["Ὄ"] = "ὄ",
["Ὅ"] = "ὅ",
["Ὑ"] = "ὑ",
["Ὓ"] = "ὓ",
["Ὕ"] = "ὕ",
["Ὗ"] = "ὗ",
["Ὠ"] = "ὠ",
["Ὡ"] = "ὡ",
["Ὢ"] = "ὢ",
["Ὣ"] = "ὣ",
["Ὤ"] = "ὤ",
["Ὥ"] = "ὥ",
["Ὦ"] = "ὦ",
["Ὧ"] = "ὧ",
["ᾈ"] = "ᾀ",
["ᾉ"] = "ᾁ",
["ᾊ"] = "ᾂ",
["ᾋ"] = "ᾃ",
["ᾌ"] = "ᾄ",
["ᾍ"] = "ᾅ",
["ᾎ"] = "ᾆ",
["ᾏ"] = "ᾇ",
["ᾘ"] = "ᾐ",
["ᾙ"] = "ᾑ",
["ᾚ"] = "ᾒ",
["ᾛ"] = "ᾓ",
["ᾜ"] = "ᾔ",
["ᾝ"] = "ᾕ",
["ᾞ"] = "ᾖ",
["ᾟ"] = "ᾗ",
["ᾨ"] = "ᾠ",
["ᾩ"] = "ᾡ",
["ᾪ"] = "ᾢ",
["ᾫ"] = "ᾣ",
["ᾬ"] = "ᾤ",
["ᾭ"] = "ᾥ",
["ᾮ"] = "ᾦ",
["ᾯ"] = "ᾧ",
["Ᾰ"] = "ᾰ",
["Ᾱ"] = "ᾱ",
["Ὰ"] = "ὰ",
["Ά"] = "ά",
["ᾼ"] = "ᾳ",
["Ὲ"] = "ὲ",
["Έ"] = "έ",
["Ὴ"] = "ὴ",
["Ή"] = "ή",
["ῌ"] = "ῃ",
["Ῐ"] = "ῐ",
["Ῑ"] = "ῑ",
["Ὶ"] = "ὶ",
["Ί"] = "ί",
["Ῠ"] = "ῠ",
["Ῡ"] = "ῡ",
["Ὺ"] = "ὺ",
["Ύ"] = "ύ",
["Ῥ"] = "ῥ",
["Ὸ"] = "ὸ",
["Ό"] = "ό",
["Ὼ"] = "ὼ",
["Ώ"] = "ώ",
["ῼ"] = "ῳ",
["Ω"] = "ω",
["K"] = "k",
["Å"] = "å",
["Ⅎ"] = "ⅎ",
["Ⅰ"] = "ⅰ",
["Ⅱ"] = "ⅱ",
["Ⅲ"] = "ⅲ",
["Ⅳ"] = "ⅳ",
["Ⅴ"] = "ⅴ",
["Ⅵ"] = "ⅵ",
["Ⅶ"] = "ⅶ",
["Ⅷ"] = "ⅷ",
["Ⅸ"] = "ⅸ",
["Ⅹ"] = "ⅹ",
["Ⅺ"] = "ⅺ",
["Ⅻ"] = "ⅻ",
["Ⅼ"] = "ⅼ",
["Ⅽ"] = "ⅽ",
["Ⅾ"] = "ⅾ",
["Ⅿ"] = "ⅿ",
["Ↄ"] = "ↄ",
["Ⓐ"] = "ⓐ",
["Ⓑ"] = "ⓑ",
["Ⓒ"] = "ⓒ",
["Ⓓ"] = "ⓓ",
["Ⓔ"] = "ⓔ",
["Ⓕ"] = "ⓕ",
["Ⓖ"] = "ⓖ",
["Ⓗ"] = "ⓗ",
["Ⓘ"] = "ⓘ",
["Ⓙ"] = "ⓙ",
["Ⓚ"] = "ⓚ",
["Ⓛ"] = "ⓛ",
["Ⓜ"] = "ⓜ",
["Ⓝ"] = "ⓝ",
["Ⓞ"] = "ⓞ",
["Ⓟ"] = "ⓟ",
["Ⓠ"] = "ⓠ",
["Ⓡ"] = "ⓡ",
["Ⓢ"] = "ⓢ",
["Ⓣ"] = "ⓣ",
["Ⓤ"] = "ⓤ",
["Ⓥ"] = "ⓥ",
["Ⓦ"] = "ⓦ",
["Ⓧ"] = "ⓧ",
["Ⓨ"] = "ⓨ",
["Ⓩ"] = "ⓩ",
["Ⰰ"] = "ⰰ",
["Ⰱ"] = "ⰱ",
["Ⰲ"] = "ⰲ",
["Ⰳ"] = "ⰳ",
["Ⰴ"] = "ⰴ",
["Ⰵ"] = "ⰵ",
["Ⰶ"] = "ⰶ",
["Ⰷ"] = "ⰷ",
["Ⰸ"] = "ⰸ",
["Ⰹ"] = "ⰹ",
["Ⰺ"] = "ⰺ",
["Ⰻ"] = "ⰻ",
["Ⰼ"] = "ⰼ",
["Ⰽ"] = "ⰽ",
["Ⰾ"] = "ⰾ",
["Ⰿ"] = "ⰿ",
["Ⱀ"] = "ⱀ",
["Ⱁ"] = "ⱁ",
["Ⱂ"] = "ⱂ",
["Ⱃ"] = "ⱃ",
["Ⱄ"] = "ⱄ",
["Ⱅ"] = "ⱅ",
["Ⱆ"] = "ⱆ",
["Ⱇ"] = "ⱇ",
["Ⱈ"] = "ⱈ",
["Ⱉ"] = "ⱉ",
["Ⱊ"] = "ⱊ",
["Ⱋ"] = "ⱋ",
["Ⱌ"] = "ⱌ",
["Ⱍ"] = "ⱍ",
["Ⱎ"] = "ⱎ",
["Ⱏ"] = "ⱏ",
["Ⱐ"] = "ⱐ",
["Ⱑ"] = "ⱑ",
["Ⱒ"] = "ⱒ",
["Ⱓ"] = "ⱓ",
["Ⱔ"] = "ⱔ",
["Ⱕ"] = "ⱕ",
["Ⱖ"] = "ⱖ",
["Ⱗ"] = "ⱗ",
["Ⱘ"] = "ⱘ",
["Ⱙ"] = "ⱙ",
["Ⱚ"] = "ⱚ",
["Ⱛ"] = "ⱛ",
["Ⱜ"] = "ⱜ",
["Ⱝ"] = "ⱝ",
["Ⱞ"] = "ⱞ",
["Ⱡ"] = "ⱡ",
["Ɫ"] = "ɫ",
["Ᵽ"] = "ᵽ",
["Ɽ"] = "ɽ",
["Ⱨ"] = "ⱨ",
["Ⱪ"] = "ⱪ",
["Ⱬ"] = "ⱬ",
["Ⱶ"] = "ⱶ",
["Ⲁ"] = "ⲁ",
["Ⲃ"] = "ⲃ",
["Ⲅ"] = "ⲅ",
["Ⲇ"] = "ⲇ",
["Ⲉ"] = "ⲉ",
["Ⲋ"] = "ⲋ",
["Ⲍ"] = "ⲍ",
["Ⲏ"] = "ⲏ",
["Ⲑ"] = "ⲑ",
["Ⲓ"] = "ⲓ",
["Ⲕ"] = "ⲕ",
["Ⲗ"] = "ⲗ",
["Ⲙ"] = "ⲙ",
["Ⲛ"] = "ⲛ",
["Ⲝ"] = "ⲝ",
["Ⲟ"] = "ⲟ",
["Ⲡ"] = "ⲡ",
["Ⲣ"] = "ⲣ",
["Ⲥ"] = "ⲥ",
["Ⲧ"] = "ⲧ",
["Ⲩ"] = "ⲩ",
["Ⲫ"] = "ⲫ",
["Ⲭ"] = "ⲭ",
["Ⲯ"] = "ⲯ",
["Ⲱ"] = "ⲱ",
["Ⲳ"] = "ⲳ",
["Ⲵ"] = "ⲵ",
["Ⲷ"] = "ⲷ",
["Ⲹ"] = "ⲹ",
["Ⲻ"] = "ⲻ",
["Ⲽ"] = "ⲽ",
["Ⲿ"] = "ⲿ",
["Ⳁ"] = "ⳁ",
["Ⳃ"] = "ⳃ",
["Ⳅ"] = "ⳅ",
["Ⳇ"] = "ⳇ",
["Ⳉ"] = "ⳉ",
["Ⳋ"] = "ⳋ",
["Ⳍ"] = "ⳍ",
["Ⳏ"] = "ⳏ",
["Ⳑ"] = "ⳑ",
["Ⳓ"] = "ⳓ",
["Ⳕ"] = "ⳕ",
["Ⳗ"] = "ⳗ",
["Ⳙ"] = "ⳙ",
["Ⳛ"] = "ⳛ",
["Ⳝ"] = "ⳝ",
["Ⳟ"] = "ⳟ",
["Ⳡ"] = "ⳡ",
["Ⳣ"] = "ⳣ",
["A"] = "a",
["B"] = "b",
["C"] = "c",
["D"] = "d",
["E"] = "e",
["F"] = "f",
["G"] = "g",
["H"] = "h",
["I"] = "i",
["J"] = "j",
["K"] = "k",
["L"] = "l",
["M"] = "m",
["N"] = "n",
["O"] = "o",
["P"] = "p",
["Q"] = "q",
["R"] = "r",
["S"] = "s",
["T"] = "t",
["U"] = "u",
["V"] = "v",
["W"] = "w",
["X"] = "x",
["Y"] = "y",
["Z"] = "z",
["𐐀"] = "𐐨",
["𐐁"] = "𐐩",
["𐐂"] = "𐐪",
["𐐃"] = "𐐫",
["𐐄"] = "𐐬",
["𐐅"] = "𐐭",
["𐐆"] = "𐐮",
["𐐇"] = "𐐯",
["𐐈"] = "𐐰",
["𐐉"] = "𐐱",
["𐐊"] = "𐐲",
["𐐋"] = "𐐳",
["𐐌"] = "𐐴",
["𐐍"] = "𐐵",
["𐐎"] = "𐐶",
["𐐏"] = "𐐷",
["𐐐"] = "𐐸",
["𐐑"] = "𐐹",
["𐐒"] = "𐐺",
["𐐓"] = "𐐻",
["𐐔"] = "𐐼",
["𐐕"] = "𐐽",
["𐐖"] = "𐐾",
["𐐗"] = "𐐿",
["𐐘"] = "𐑀",
["𐐙"] = "𐑁",
["𐐚"] = "𐑂",
["𐐛"] = "𐑃",
["𐐜"] = "𐑄",
["𐐝"] = "𐑅",
["𐐞"] = "𐑆",
["𐐟"] = "𐑇",
["𐐠"] = "𐑈",
["𐐡"] = "𐑉",
["𐐢"] = "𐑊",
["𐐣"] = "𐑋",
["𐐤"] = "𐑌",
["𐐥"] = "𐑍",
["𐐦"] = "𐑎",
["𐐧"] = "𐑏",
}
================================================
FILE: gamemode/core/libs/thirdparty/sh_cami.lua
================================================
--[[
CAMI - Common Admin Mod Interface.
Copyright 2020 CAMI Contributors
Makes admin mods intercompatible and provides an abstract privilege interface
for third party addons.
Follows the specification on this page:
https://github.com/glua/CAMI/blob/master/README.md
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
-- Version number in YearMonthDay format.
local version = 20211019
if CAMI and CAMI.Version >= version then return end
CAMI = CAMI or {}
CAMI.Version = version
--- @class CAMI_USERGROUP
--- defines the charactaristics of a usergroup
--- @field Name string @The name of the usergroup
--- @field Inherits string @The name of the usergroup this usergroup inherits from
--- @field CAMI_Source string @The source specified by the admin mod which registered this usergroup (if any, converted to a string)
--- @class CAMI_PRIVILEGE
--- defines the charactaristics of a privilege
--- @field Name string @The name of the privilege
--- @field MinAccess "'user'" | "'admin'" | "'superadmin'" @Default group that should have this privilege
--- @field Description string | nil @Optional text describing the purpose of the privilege
local CAMI_PRIVILEGE = {}
--- Optional function to check if a player has access to this privilege
--- (and optionally execute it on another player)
---
--- ⚠ **Warning**: This function may not be called by all admin mods
--- @param actor GPlayer @The player
--- @param target GPlayer | nil @Optional - the target
--- @return boolean @If they can or not
--- @return string | nil @Optional reason
function CAMI_PRIVILEGE:HasAccess(actor, target)
end
--- Contains the registered CAMI_USERGROUP usergroup structures.
--- Indexed by usergroup name.
--- @type CAMI_USERGROUP[]
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
user = {
Name = "user",
Inherits = "user",
CAMI_Source = "Garry's Mod",
},
admin = {
Name = "admin",
Inherits = "user",
CAMI_Source = "Garry's Mod",
},
superadmin = {
Name = "superadmin",
Inherits = "admin",
CAMI_Source = "Garry's Mod",
}
}
--- Contains the registered CAMI_PRIVILEGE privilege structures.
--- Indexed by privilege name.
--- @type CAMI_PRIVILEGE[]
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
--- Registers a usergroup with CAMI.
---
--- Use the source parameter to make sure CAMI.RegisterUsergroup function and
--- the CAMI.OnUsergroupRegistered hook don't cause an infinite loop
--- @param usergroup CAMI_USERGROUP @The structure for the usergroup you want to register
--- @param source any @Identifier for your own admin mod. Can be anything.
--- @return CAMI_USERGROUP @The usergroup given as an argument
function CAMI.RegisterUsergroup(usergroup, source)
if source then
usergroup.CAMI_Source = tostring(source)
end
usergroups[usergroup.Name] = usergroup
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
return usergroup
end
--- Unregisters a usergroup from CAMI. This will call a hook that will notify
--- all other admin mods of the removal.
---
--- ⚠ **Warning**: Call only when the usergroup is to be permanently removed.
---
--- Use the source parameter to make sure CAMI.UnregisterUsergroup function and
--- the CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
--- @param usergroupName string @The name of the usergroup.
--- @param source any @Identifier for your own admin mod. Can be anything.
--- @return boolean @Whether the unregistering succeeded.
function CAMI.UnregisterUsergroup(usergroupName, source)
if not usergroups[usergroupName] then return false end
local usergroup = usergroups[usergroupName]
usergroups[usergroupName] = nil
hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source)
return true
end
--- Retrieves all registered usergroups.
--- @return CAMI_USERGROUP[] @Usergroups indexed by their names.
function CAMI.GetUsergroups()
return usergroups
end
--- Receives information about a usergroup.
--- @param usergroupName string
--- @return CAMI_USERGROUP | nil @Returns nil when the usergroup does not exist.
function CAMI.GetUsergroup(usergroupName)
return usergroups[usergroupName]
end
--- Checks to see if potentialAncestor is an ancestor of usergroupName.
--- All usergroups are ancestors of themselves.
---
--- Examples:
--- * `user` is an ancestor of `admin` and also `superadmin`
--- * `admin` is an ancestor of `superadmin`, but not `user`
--- @param usergroupName string @The usergroup to query
--- @param potentialAncestor string @The ancestor to query
--- @return boolean @Whether usergroupName inherits potentialAncestor.
function CAMI.UsergroupInherits(usergroupName, potentialAncestor)
repeat
if usergroupName == potentialAncestor then return true end
usergroupName = usergroups[usergroupName] and
usergroups[usergroupName].Inherits or
usergroupName
until not usergroups[usergroupName] or
usergroups[usergroupName].Inherits == usergroupName
-- One can only be sure the usergroup inherits from user if the
-- usergroup isn't registered.
return usergroupName == potentialAncestor or potentialAncestor == "user"
end
--- Find the base group a usergroup inherits from.
---
--- This function traverses down the inheritence chain, so for example if you have
--- `user` -> `group1` -> `group2`
--- this function will return `user` if you pass it `group2`.
---
--- ℹ **NOTE**: All usergroups must eventually inherit either user, admin or superadmin.
--- @param usergroupName string @The name of the usergroup
--- @return "'user'" | "'admin'" | "'superadmin'" @The name of the root usergroup
function CAMI.InheritanceRoot(usergroupName)
if not usergroups[usergroupName] then return end
local inherits = usergroups[usergroupName].Inherits
while inherits ~= usergroups[usergroupName].Inherits do
usergroupName = usergroups[usergroupName].Inherits
end
return usergroupName
end
--- Registers an addon privilege with CAMI.
---
--- ⚠ **Warning**: This should only be used by addons. Admin mods must *NOT*
--- register their privileges using this function.
--- @param privilege CAMI_PRIVILEGE
--- @return CAMI_PRIVILEGE @The privilege given as argument.
function CAMI.RegisterPrivilege(privilege)
privileges[privilege.Name] = privilege
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
return privilege
end
--- Unregisters a privilege from CAMI.
--- This will call a hook that will notify any admin mods of the removal.
---
--- ⚠ **Warning**: Call only when the privilege is to be permanently removed.
--- @param privilegeName string @The name of the privilege.
--- @return boolean @Whether the unregistering succeeded.
function CAMI.UnregisterPrivilege(privilegeName)
if not privileges[privilegeName] then return false end
local privilege = privileges[privilegeName]
privileges[privilegeName] = nil
hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege)
return true
end
--- Retrieves all registered privileges.
--- @return CAMI_PRIVILEGE[] @All privileges indexed by their names.
function CAMI.GetPrivileges()
return privileges
end
--- Receives information about a privilege.
--- @param privilegeName string
--- @return CAMI_PRIVILEGE | nil
function CAMI.GetPrivilege(privilegeName)
return privileges[privilegeName]
end
-- Default access handler
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
function(_, actorPly, privilegeName, callback, targetPly, extraInfoTbl)
-- The server always has access in the fallback
if not IsValid(actorPly) then return callback(true, "Fallback.") end
local priv = privileges[privilegeName]
local fallback = extraInfoTbl and (
not extraInfoTbl.Fallback and actorPly:IsAdmin() or
extraInfoTbl.Fallback == "user" and true or
extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or
extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin())
if not priv then return callback(fallback, "Fallback.") end
local hasAccess =
priv.MinAccess == "user" or
priv.MinAccess == "admin" and actorPly:IsAdmin() or
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
if hasAccess and priv.HasAccess then
hasAccess = priv:HasAccess(actorPly, targetPly)
end
callback(hasAccess, "Fallback.")
end,
["CAMI.SteamIDHasAccess"] =
function(_, _, _, callback)
callback(false, "No information available.")
end
}
--- @class CAMI_ACCESS_EXTRA_INFO
--- @field Fallback "'user'" | "'admin'" | "'superadmin'" @Fallback status for if the privilege doesn't exist. Defaults to `admin`.
--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have.
--- @field CommandArguments table @Extra arguments that were given to the privilege command.
--- Checks if a player has access to a privilege
--- (and optionally can execute it on targetPly)
---
--- This function is designed to be asynchronous but will be invoked
--- synchronously if no callback is passed.
---
--- ⚠ **Warning**: If the currently installed admin mod does not support
--- synchronous queries, this function will throw an error!
--- @param actorPly GPlayer @The player to query
--- @param privilegeName string @The privilege to query
--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer, or nil for synchronous
--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban)
--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
--- @return boolean | nil @Synchronous only - if the player has the privilege
--- @return string | nil @Synchronous only - optional reason from admin mod
function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly,
extraInfoTbl)
local hasAccess, reason = nil, nil
local callback_ = callback or function(hA, r) hasAccess, reason = hA, r end
hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly,
privilegeName, callback_, targetPly, extraInfoTbl)
if callback ~= nil then return end
if hasAccess == nil then
local err = [[The function CAMI.PlayerHasAccess was used to find out
whether Player %s has privilege "%s", but an admin mod did not give an
immediate answer!]]
error(string.format(err,
actorPly:IsPlayer() and actorPly:Nick() or tostring(actorPly),
privilegeName))
end
return hasAccess, reason
end
--- Get all the players on the server with a certain privilege
--- (and optionally who can execute it on targetPly)
---
--- ℹ **NOTE**: This is an asynchronous function!
--- @param privilegeName string @The privilege to query
--- @param callback fun(players: GPlayer[]) @Callback to receive the answer
--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban)
--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly,
extraInfoTbl)
local allowedPlys = {}
local allPlys = player.GetAll()
local countdown = #allPlys
local function onResult(ply, hasAccess, _)
countdown = countdown - 1
if hasAccess then table.insert(allowedPlys, ply) end
if countdown == 0 then callback(allowedPlys) end
end
for _, ply in ipairs(allPlys) do
CAMI.PlayerHasAccess(ply, privilegeName,
function(...) onResult(ply, ...) end,
targetPly, extraInfoTbl)
end
end
--- @class CAMI_STEAM_ACCESS_EXTRA_INFO
--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have.
--- @field CommandArguments table @Extra arguments that were given to the privilege command.
--- Checks if a (potentially offline) SteamID has access to a privilege
--- (and optionally if they can execute it on a target SteamID)
---
--- ℹ **NOTE**: This is an asynchronous function!
--- @param actorSteam string | nil @The SteamID to query
--- @param privilegeName string @The privilege to query
--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer
--- @param targetSteam string | nil @Optional - target SteamID for if the privilege effects another player (eg kick/ban)
--- @param extraInfoTbl CAMI_STEAM_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
targetSteam, extraInfoTbl)
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
privilegeName, callback, targetSteam, extraInfoTbl)
end
--- Signify that your admin mod has changed the usergroup of a player. This
--- function communicates to other admin mods what it thinks the usergroup
--- of a player should be.
---
--- Listen to the hook to receive the usergroup changes of other admin mods.
--- @param ply GPlayer @The player for which the usergroup is changed
--- @param old string @The previous usergroup of the player.
--- @param new string @The new usergroup of the player.
--- @param source any @Identifier for your own admin mod. Can be anything.
function CAMI.SignalUserGroupChanged(ply, old, new, source)
hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source)
end
--- Signify that your admin mod has changed the usergroup of a disconnected
--- player. This communicates to other admin mods what it thinks the usergroup
--- of a player should be.
---
--- Listen to the hook to receive the usergroup changes of other admin mods.
--- @param steamId string @The steam ID of the player for which the usergroup is changed
--- @param old string @The previous usergroup of the player.
--- @param new string @The new usergroup of the player.
--- @param source any @Identifier for your own admin mod. Can be anything.
function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source)
hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source)
end
================================================
FILE: gamemode/core/libs/thirdparty/sh_date.lua
================================================
---------------------------------------------------------------------------------------
-- Module for date and time calculations
--
-- Version 2.1.1
-- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com)
-- Copyright (C) 2013-2014, by Thijs Schreijer
-- Licensed under MIT, http://opensource.org/licenses/MIT
-- https://github.com/Tieske/date
--[[
The MIT License (MIT) http://opensource.org/licenses/MIT
Copyright (c) 2013-2017 Thijs Schreijer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]
--[[ CONSTANTS ]]--
local HOURPERDAY = 24
local MINPERHOUR = 60
local MINPERDAY = 1440 -- 24*60
local SECPERMIN = 60
local SECPERHOUR = 3600 -- 60*60
local SECPERDAY = 86400 -- 24*60*60
local TICKSPERSEC = 1000000
local TICKSPERDAY = 86400000000
local TICKSPERHOUR = 3600000000
local TICKSPERMIN = 60000000
local DAYNUM_MAX = 365242500 -- Sat Jan 01 1000000 00:00:00
local DAYNUM_MIN = -365242500 -- Mon Jan 01 1000000 BCE 00:00:00
local DAYNUM_DEF = 0 -- Mon Jan 01 0001 00:00:00
local _;
--[[ LOCAL ARE FASTER ]]--
local type = type
local pairs = pairs
local error = error
local assert = assert
local tonumber = tonumber
local tostring = tostring
local string = string
local math = math
local os = os
local unpack = unpack or table.unpack
local pack = table.pack or function(...) return { n = select('#', ...), ... } end
local setmetatable = setmetatable
local getmetatable = getmetatable
--[[ EXTRA FUNCTIONS ]]--
local fmt = string.format
local lwr = string.lower
local upr = string.upper
local rep = string.rep
local len = string.len
local sub = string.sub
local gsub = string.gsub
local gmatch = string.gmatch or string.gfind
local find = string.find
local ostime = os.time
local osdate = os.date
local floor = math.floor
local ceil = math.ceil
local abs = math.abs
-- removes the decimal part of a number
local function fix(n) n = tonumber(n) return n and ((n > 0 and floor or ceil)(n)) end
-- returns the modulo n % d;
local function mod(n,d) return n - d*floor(n/d) end
-- rounds a number;
local function round(n, d) d=d^10 return floor((n*d)+.5)/d end
-- rounds a number to whole;
local function whole(n)return floor(n+.5)end
-- is `str` in string list `tbl`, `ml` is the minimun len
local function inlist(str, tbl, ml, tn)
local sl = len(str)
if sl < (ml or 0) then return nil end
str = lwr(str)
for k, v in pairs(tbl) do
if str == lwr(sub(v, 1, sl)) then
if tn then tn[0] = k end
return k
end
end
end
local function fnil() end
local function fret(x)return x;end
--[[ DATE FUNCTIONS ]]--
local DATE_EPOCH -- to be set later
local sl_weekdays = {
[0]="Sunday",[1]="Monday",[2]="Tuesday",[3]="Wednesday",[4]="Thursday",[5]="Friday",[6]="Saturday",
[7]="Sun",[8]="Mon",[9]="Tue",[10]="Wed",[11]="Thu",[12]="Fri",[13]="Sat",
}
local sl_meridian = {[-1]="AM", [1]="PM"}
local sl_months = {
[00]="January", [01]="February", [02]="March",
[03]="April", [04]="May", [05]="June",
[06]="July", [07]="August", [08]="September",
[09]="October", [10]="November", [11]="December",
[12]="Jan", [13]="Feb", [14]="Mar",
[15]="Apr", [16]="May", [17]="Jun",
[18]="Jul", [19]="Aug", [20]="Sep",
[21]="Oct", [22]="Nov", [23]="Dec",
}
-- added the '.2' to avoid collision, use `fix` to remove
local sl_timezone = {
[000]="utc", [0.2]="gmt",
[300]="est", [240]="edt",
[360]="cst", [300.2]="cdt",
[420]="mst", [360.2]="mdt",
[480]="pst", [420.2]="pdt",
}
-- set the day fraction resolution
local function setticks(t)
TICKSPERSEC = t;
TICKSPERDAY = SECPERDAY*TICKSPERSEC
TICKSPERHOUR= SECPERHOUR*TICKSPERSEC
TICKSPERMIN = SECPERMIN*TICKSPERSEC
end
-- is year y leap year?
local function isleapyear(y) -- y must be int!
return (mod(y, 4) == 0 and (mod(y, 100) ~= 0 or mod(y, 400) == 0))
end
-- day since year 0
local function dayfromyear(y) -- y must be int!
return 365*y + floor(y/4) - floor(y/100) + floor(y/400)
end
-- day number from date, month is zero base
local function makedaynum(y, m, d)
local mm = mod(mod(m,12) + 10, 12)
return dayfromyear(y + floor(m/12) - floor(mm/10)) + floor((mm*306 + 5)/10) + d - 307
--local yy = y + floor(m/12) - floor(mm/10)
--return dayfromyear(yy) + floor((mm*306 + 5)/10) + (d - 1)
end
-- date from day number, month is zero base
local function breakdaynum(g)
local g = g + 306
local y = floor((10000*g + 14780)/3652425)
local d = g - dayfromyear(y)
if d < 0 then y = y - 1; d = g - dayfromyear(y) end
local mi = floor((100*d + 52)/3060)
return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1)
end
--[[ for floats or int32 Lua Number data type
local function breakdaynum2(g)
local g, n = g + 306;
local n400 = floor(g/DI400Y);n = mod(g,DI400Y);
local n100 = floor(n/DI100Y);n = mod(n,DI100Y);
local n004 = floor(n/DI4Y); n = mod(n,DI4Y);
local n001 = floor(n/365); n = mod(n,365);
local y = (n400*400) + (n100*100) + (n004*4) + n001 - ((n001 == 4 or n100 == 4) and 1 or 0)
local d = g - dayfromyear(y)
local mi = floor((100*d + 52)/3060)
return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1)
end
]]
-- day fraction from time
local function makedayfrc(h,r,s,t)
return ((h*60 + r)*60 + s)*TICKSPERSEC + t
end
-- time from day fraction
local function breakdayfrc(df)
return
mod(floor(df/TICKSPERHOUR),HOURPERDAY),
mod(floor(df/TICKSPERMIN ),MINPERHOUR),
mod(floor(df/TICKSPERSEC ),SECPERMIN),
mod(df,TICKSPERSEC)
end
-- weekday sunday = 0, monday = 1 ...
local function weekday(dn) return mod(dn + 1, 7) end
-- yearday 0 based ...
local function yearday(dn)
return dn - dayfromyear((breakdaynum(dn))-1)
end
-- parse v as a month
local function getmontharg(v)
local m = tonumber(v);
return (m and fix(m - 1)) or inlist(tostring(v) or "", sl_months, 2)
end
-- get daynum of isoweek one of year y
local function isow1(y)
local f = makedaynum(y, 0, 4) -- get the date for the 4-Jan of year `y`
local d = weekday(f)
d = d == 0 and 7 or d -- get the ISO day number, 1 == Monday, 7 == Sunday
return f + (1 - d)
end
local function isowy(dn)
local w1;
local y = (breakdaynum(dn))
if dn >= makedaynum(y, 11, 29) then
w1 = isow1(y + 1);
if dn < w1 then
w1 = isow1(y);
else
y = y + 1;
end
else
w1 = isow1(y);
if dn < w1 then
w1 = isow1(y-1)
y = y - 1
end
end
return floor((dn-w1)/7)+1, y
end
local function isoy(dn)
local y = (breakdaynum(dn))
return y + (((dn >= makedaynum(y, 11, 29)) and (dn >= isow1(y + 1))) and 1 or (dn < isow1(y) and -1 or 0))
end
local function makedaynum_isoywd(y,w,d)
return isow1(y) + 7*w + d - 8 -- simplified: isow1(y) + ((w-1)*7) + (d-1)
end
--[[ THE DATE MODULE ]]--
local fmtstr = "%x %X";
--#if not DATE_OBJECT_AFX then
local date = {}
setmetatable(date, date)
-- Version: VMMMRRRR; V-Major, M-Minor, R-Revision; e.g. 5.45.321 == 50450321
date.version = 20010001 -- 2.1.1
--#end -- not DATE_OBJECT_AFX
--[[ THE DATE OBJECT ]]--
local dobj = {}
dobj.__index = dobj
dobj.__metatable = dobj
-- shout invalid arg
local function date_error_arg() return error("invalid argument(s)",0) end
-- create new date object
local function date_new(dn, df)
return setmetatable({daynum=dn, dayfrc=df}, dobj)
end
-- is `v` a date object?
local function date_isdobj(v)
return (istable(v) and getmetatable(v) == dobj) and v
end
--#if not NO_LOCAL_TIME_SUPPORT then
-- magic year table
local date_epoch, yt;
local function getequivyear(y)
assert(not yt)
yt = {}
local de, dw, dy = date_epoch:copy()
for i = 0, 3000 do
de:setyear(de:getyear() + 1, 1, 1)
dy = de:getyear()
dw = de:getweekday() * (isleapyear(dy) and -1 or 1)
if not yt[dw] then yt[dw] = dy end --print(de)
if yt[1] and yt[2] and yt[3] and yt[4] and yt[5] and yt[6] and yt[7] and yt[-1] and yt[-2] and yt[-3] and yt[-4] and yt[-5] and yt[-6] and yt[-7] then
getequivyear = function(y) return yt[ (weekday(makedaynum(y, 0, 1)) + 1) * (isleapyear(y) and -1 or 1) ] end
return getequivyear(y)
end
end
end
-- TimeValue from daynum and dayfrc
local function dvtotv(dn, df)
return fix(dn - DATE_EPOCH) * SECPERDAY + (df/1000)
end
-- TimeValue from date and time
local function totv(y,m,d,h,r,s)
return (makedaynum(y, m, d) - DATE_EPOCH) * SECPERDAY + ((h*60 + r)*60 + s)
end
-- TimeValue from TimeTable
local function tmtotv(tm)
return tm and totv(tm.year, tm.month - 1, tm.day, tm.hour, tm.min, tm.sec)
end
-- Returns the bias in seconds of utc time daynum and dayfrc
local function getbiasutc2(self)
local y,m,d = breakdaynum(self.daynum)
local h,r,s = breakdayfrc(self.dayfrc)
local tvu = totv(y,m,d,h,r,s) -- get the utc TimeValue of date and time
local tml = osdate("*t", tvu) -- get the local TimeTable of tvu
if (not tml) or (tml.year > (y+1) or tml.year < (y-1)) then -- failed try the magic
y = getequivyear(y)
tvu = totv(y,m,d,h,r,s)
tml = osdate("*t", tvu)
end
local tvl = tmtotv(tml)
if tvu and tvl then
return tvu - tvl, tvu, tvl
else
return error("failed to get bias from utc time")
end
end
-- Returns the bias in seconds of local time daynum and dayfrc
local function getbiasloc2(daynum, dayfrc)
local tvu
-- extract date and time
local y,m,d = breakdaynum(daynum)
local h,r,s = breakdayfrc(dayfrc)
-- get equivalent TimeTable
local tml = {year=y, month=m+1, day=d, hour=h, min=r, sec=s}
-- get equivalent TimeValue
local tvl = tmtotv(tml)
local function chkutc()
tml.isdst = nil; local tvug = ostime(tml) if tvug and (tvl == tmtotv(osdate("*t", tvug))) then tvu = tvug return end
tml.isdst = true; local tvud = ostime(tml) if tvud and (tvl == tmtotv(osdate("*t", tvud))) then tvu = tvud return end
tvu = tvud or tvug
end
chkutc()
if not tvu then
tml.year = getequivyear(y)
tvl = tmtotv(tml)
chkutc()
end
return ((tvu and tvl) and (tvu - tvl)) or error("failed to get bias from local time"), tvu, tvl
end
--#end -- not NO_LOCAL_TIME_SUPPORT
--#if not DATE_OBJECT_AFX then
-- the date parser
local strwalker = {} -- ^Lua regular expression is not as powerful as Perl$
strwalker.__index = strwalker
local function newstrwalker(s)return setmetatable({s=s, i=1, e=1, c=len(s)}, strwalker) end
function strwalker:aimchr() return "\n" .. self.s .. "\n" .. rep(".",self.e-1) .. "^" end
function strwalker:finish() return self.i > self.c end
function strwalker:back() self.i = self.e return self end
function strwalker:restart() self.i, self.e = 1, 1 return self end
function strwalker:match(s) return (find(self.s, s, self.i)) end
function strwalker:__call(s, f)-- print("strwalker:__call "..s..self:aimchr())
local is, ie; is, ie, self[1], self[2], self[3], self[4], self[5] = find(self.s, s, self.i)
if is then self.e, self.i = self.i, 1+ie; if f then f(unpack(self)) end return self end
end
local function date_parse(str)
local y,m,d, h,r,s, z, w,u, j, e, k, x,v,c, chkfin, dn,df;
local sw = newstrwalker(gsub(gsub(str, "(%b())", ""),"^(%s*)","")) -- remove comment, trim leading space
--local function error_out() print(y,m,d,h,r,s) end
local function error_dup(q) --[[error_out()]] error("duplicate value: " .. (q or "") .. sw:aimchr()) end
local function error_syn(q) --[[error_out()]] error("syntax error: " .. (q or "") .. sw:aimchr()) end
local function error_inv(q) --[[error_out()]] error("invalid date: " .. (q or "") .. sw:aimchr()) end
local function sety(q) y = y and error_dup() or tonumber(q); end
local function setm(q) m = (m or w or j) and error_dup(m or w or j) or tonumber(q) end
local function setd(q) d = d and error_dup() or tonumber(q) end
local function seth(q) h = h and error_dup() or tonumber(q) end
local function setr(q) r = r and error_dup() or tonumber(q) end
local function sets(q) s = s and error_dup() or tonumber(q) end
local function adds(q) s = s + tonumber(q) end
local function setj(q) j = (m or w or j) and error_dup() or tonumber(q); end
local function setz(q) z = (z ~= 0 and z) and error_dup() or q end
local function setzn(zs,zn) zn = tonumber(zn); setz( ((zn<24) and (zn*60) or (mod(zn,100) + floor(zn/100) * 60))*( zs=='+' and -1 or 1) ) end
local function setzc(zs,zh,zm) setz( ((tonumber(zh)*60) + tonumber(zm))*( zs=='+' and -1 or 1) ) end
if not (sw("^(%d%d%d%d)",sety) and (sw("^(%-?)(%d%d)%1(%d%d)",function(_,a,b) setm(tonumber(a)); setd(tonumber(b)) end) or sw("^(%-?)[Ww](%d%d)%1(%d?)",function(_,a,b) w, u = tonumber(a), tonumber(b or 1) end) or sw("^%-?(%d%d%d)",setj) or sw("^%-?(%d%d)",function(a) setm(a);setd(1) end))
and ((sw("^%s*[Tt]?(%d%d):?",seth) and sw("^(%d%d):?",setr) and sw("^(%d%d)",sets) and sw("^(%.%d+)",adds))
or sw:finish() or (sw"^%s*$" or sw"^%s*[Zz]%s*$" or sw("^%s-([%+%-])(%d%d):?(%d%d)%s*$",setzc) or sw("^%s*([%+%-])(%d%d)%s*$",setzn))
) )
then --print(y,m,d,h,r,s,z,w,u,j)
sw:restart(); y,m,d,h,r,s,z,w,u,j = nil;
repeat -- print(sw:aimchr())
if sw("^[tT:]?%s*(%d%d?):",seth) then --print("$Time")
_ = sw("^%s*(%d%d?)",setr) and sw("^%s*:%s*(%d%d?)",sets) and sw("^(%.%d+)",adds)
elseif sw("^(%d+)[/\\%s,-]?%s*") then --print("$Digits")
x, c = tonumber(sw[1]), len(sw[1])
if (x >= 70) or (m and d and (not y)) or (c > 3) then
sety( x + ((x >= 100 or c>3)and 0 or 1900) )
else
if m then setd(x) else m = x end
end
elseif sw("^(%a+)[/\\%s,-]?%s*") then --print("$Words")
x = sw[1]
if inlist(x, sl_months, 2, sw) then
if m and (not d) and (not y) then d, m = m, false end
setm(mod(sw[0],12)+1)
elseif inlist(x, sl_timezone, 2, sw) then
c = fix(sw[0]) -- ignore gmt and utc
if c ~= 0 then setz(c, x) end
elseif inlist(x, sl_weekdays, 2, sw) then
k = sw[0]
else
sw:back()
-- am pm bce ad ce bc
if sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*[Ee]%s*(%2)%s*") or sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*") then
e = e and error_dup() or -1
elseif sw("^([aA])%s*(%.?)%s*[Dd]%s*(%2)%s*") or sw("^([cC])%s*(%.?)%s*[Ee]%s*(%2)%s*") then
e = e and error_dup() or 1
elseif sw("^([PApa])%s*(%.?)%s*[Mm]?%s*(%2)%s*") then
x = lwr(sw[1]) -- there should be hour and it must be correct
if (not h) or (h > 12) or (h < 0) then return error_inv() end
if x == 'a' and h == 12 then h = 0 end -- am
if x == 'p' and h ~= 12 then h = h + 12 end -- pm
else error_syn() end
end
elseif not(sw("^([+-])(%d%d?):(%d%d)",setzc) or sw("^([+-])(%d+)",setzn) or sw("^[Zz]%s*$")) then -- sw{"([+-])",{"(%d%d?):(%d%d)","(%d+)"}}
error_syn("?")
end
sw("^%s*") until sw:finish()
--else print("$Iso(Date|Time|Zone)")
end
-- if date is given, it must be complete year, month & day
if (not y and not h) or ((m and not d) or (d and not m)) or ((m and w) or (m and j) or (j and w)) then return error_inv("!") end
-- fix month
if m then m = m - 1 end
-- fix year if we are on BCE
if e and e < 0 and y > 0 then y = 1 - y end
-- create date object
dn = (y and ((w and makedaynum_isoywd(y,w,u)) or (j and makedaynum(y, 0, j)) or makedaynum(y, m, d))) or DAYNUM_DEF
df = makedayfrc(h or 0, r or 0, s or 0, 0) + ((z or 0)*TICKSPERMIN)
--print("Zone",h,r,s,z,m,d,y,df)
return date_new(dn, df) -- no need to :normalize();
end
local function date_fromtable(v)
local y, m, d = fix(v.year), getmontharg(v.month), fix(v.day)
local h, r, s, t = tonumber(v.hour), tonumber(v.min), tonumber(v.sec), tonumber(v.ticks)
-- atleast there is time or complete date
if (y or m or d) and (not(y and m and d)) then return error("incomplete table") end
return (y or h or r or s or t) and date_new(y and makedaynum(y, m, d) or DAYNUM_DEF, makedayfrc(h or 0, r or 0, s or 0, t or 0))
end
local tmap = {
['number'] = function(v) return date_epoch:copy():addseconds(v) end,
['string'] = function(v) return date_parse(v) end,
['boolean']= function(v) return date_fromtable(osdate(v and "!*t" or "*t")) end,
['table'] = function(v) local ref = getmetatable(v) == dobj; return ref and v or date_fromtable(v), ref end
}
local function date_getdobj(v)
local o, r = (tmap[type(v)] or fnil)(v);
return (o and o:normalize() or error"invalid date time value"), r -- if r is true then o is a reference to a date obj
end
--#end -- not DATE_OBJECT_AFX
local function date_from(...)
local arg = pack(...)
local y, m, d = fix(arg[1]), getmontharg(arg[2]), fix(arg[3])
local h, r, s, t = tonumber(arg[4] or 0), tonumber(arg[5] or 0), tonumber(arg[6] or 0), tonumber(arg[7] or 0)
if y and m and d and h and r and s and t then
return date_new(makedaynum(y, m, d), makedayfrc(h, r, s, t)):normalize()
else
return date_error_arg()
end
end
--[[ THE DATE OBJECT METHODS ]]--
function dobj:normalize()
local dn, df = fix(self.daynum), self.dayfrc
self.daynum, self.dayfrc = dn + floor(df/TICKSPERDAY), mod(df, TICKSPERDAY)
return (dn >= DAYNUM_MIN and dn <= DAYNUM_MAX) and self or error("date beyond imposed limits:"..self)
end
function dobj:getdate() local y, m, d = breakdaynum(self.daynum) return y, m+1, d end
function dobj:gettime() return breakdayfrc(self.dayfrc) end
function dobj:getclockhour() local h = self:gethours() return h>12 and mod(h,12) or (h==0 and 12 or h) end
function dobj:getyearday() return yearday(self.daynum) + 1 end
function dobj:getweekday() return weekday(self.daynum) + 1 end -- in lua weekday is sunday = 1, monday = 2 ...
function dobj:getyear() local r,_,_ = breakdaynum(self.daynum) return r end
function dobj:getmonth() local _,r,_ = breakdaynum(self.daynum) return r+1 end-- in lua month is 1 base
function dobj:getday() local _,_,r = breakdaynum(self.daynum) return r end
function dobj:gethours() return mod(floor(self.dayfrc/TICKSPERHOUR),HOURPERDAY) end
function dobj:getminutes() return mod(floor(self.dayfrc/TICKSPERMIN), MINPERHOUR) end
function dobj:getseconds() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN) end
function dobj:getfracsec() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN)+(mod(self.dayfrc,TICKSPERSEC)/TICKSPERSEC) end
function dobj:getticks(u) local x = mod(self.dayfrc,TICKSPERSEC) return u and ((x*u)/TICKSPERSEC) or x end
function dobj:getweeknumber(wdb)
local wd, yd = weekday(self.daynum), yearday(self.daynum)
if wdb then
wdb = tonumber(wdb)
if wdb then
wd = mod(wd-(wdb-1),7)-- shift the week day base
else
return date_error_arg()
end
end
return (yd < wd and 0) or (floor(yd/7) + ((mod(yd, 7)>=wd) and 1 or 0))
end
function dobj:getisoweekday() return mod(weekday(self.daynum)-1,7)+1 end -- sunday = 7, monday = 1 ...
function dobj:getisoweeknumber() return (isowy(self.daynum)) end
function dobj:getisoyear() return isoy(self.daynum) end
function dobj:getisodate()
local w, y = isowy(self.daynum)
return y, w, self:getisoweekday()
end
function dobj:setisoyear(y, w, d)
local cy, cw, cd = self:getisodate()
if y then cy = fix(tonumber(y))end
if w then cw = fix(tonumber(w))end
if d then cd = fix(tonumber(d))end
if cy and cw and cd then
self.daynum = makedaynum_isoywd(cy, cw, cd)
return self:normalize()
else
return date_error_arg()
end
end
function dobj:setisoweekday(d) return self:setisoyear(nil, nil, d) end
function dobj:setisoweeknumber(w,d) return self:setisoyear(nil, w, d) end
function dobj:setyear(y, m, d)
local cy, cm, cd = breakdaynum(self.daynum)
if y then cy = fix(tonumber(y))end
if m then cm = getmontharg(m) end
if d then cd = fix(tonumber(d))end
if cy and cm and cd then
self.daynum = makedaynum(cy, cm, cd)
return self:normalize()
else
return date_error_arg()
end
end
function dobj:setmonth(m, d)return self:setyear(nil, m, d) end
function dobj:setday(d) return self:setyear(nil, nil, d) end
function dobj:sethours(h, m, s, t)
local ch,cm,cs,ck = breakdayfrc(self.dayfrc)
ch, cm, cs, ck = tonumber(h or ch), tonumber(m or cm), tonumber(s or cs), tonumber(t or ck)
if ch and cm and cs and ck then
self.dayfrc = makedayfrc(ch, cm, cs, ck)
return self:normalize()
else
return date_error_arg()
end
end
function dobj:setminutes(m,s,t) return self:sethours(nil, m, s, t) end
function dobj:setseconds(s, t) return self:sethours(nil, nil, s, t) end
function dobj:setticks(t) return self:sethours(nil, nil, nil, t) end
function dobj:spanticks() return (self.daynum*TICKSPERDAY + self.dayfrc) end
function dobj:spanseconds() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERSEC end
function dobj:spanminutes() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERMIN end
function dobj:spanhours() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERHOUR end
function dobj:spandays() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERDAY end
function dobj:addyears(y, m, d)
local cy, cm, cd = breakdaynum(self.daynum)
if y then y = fix(tonumber(y))else y = 0 end
if m then m = fix(tonumber(m))else m = 0 end
if d then d = fix(tonumber(d))else d = 0 end
if y and m and d then
self.daynum = makedaynum(cy+y, cm+m, cd+d)
return self:normalize()
else
return date_error_arg()
end
end
function dobj:addmonths(m, d)
return self:addyears(nil, m, d)
end
local function dobj_adddayfrc(self,n,pt,pd)
n = tonumber(n)
if n then
local x = floor(n/pd);
self.daynum = self.daynum + x;
self.dayfrc = self.dayfrc + (n-x*pd)*pt;
return self:normalize()
else
return date_error_arg()
end
end
function dobj:adddays(n) return dobj_adddayfrc(self,n,TICKSPERDAY,1) end
function dobj:addhours(n) return dobj_adddayfrc(self,n,TICKSPERHOUR,HOURPERDAY) end
function dobj:addminutes(n) return dobj_adddayfrc(self,n,TICKSPERMIN,MINPERDAY) end
function dobj:addseconds(n) return dobj_adddayfrc(self,n,TICKSPERSEC,SECPERDAY) end
function dobj:addticks(n) return dobj_adddayfrc(self,n,1,TICKSPERDAY) end
local tvspec = {
-- Abbreviated weekday name (Sun)
['%a']=function(self) return sl_weekdays[weekday(self.daynum) + 7] end,
-- Full weekday name (Sunday)
['%A']=function(self) return sl_weekdays[weekday(self.daynum)] end,
-- Abbreviated month name (Dec)
['%b']=function(self) return sl_months[self:getmonth() - 1 + 12] end,
-- Full month name (December)
['%B']=function(self) return sl_months[self:getmonth() - 1] end,
-- Year/100 (19, 20, 30)
['%C']=function(self) return fmt("%.2d", fix(self:getyear()/100)) end,
-- The day of the month as a number (range 1 - 31)
['%d']=function(self) return fmt("%.2d", self:getday()) end,
-- year for ISO 8601 week, from 00 (79)
['%g']=function(self) return fmt("%.2d", mod(self:getisoyear() ,100)) end,
-- year for ISO 8601 week, from 0000 (1979)
['%G']=function(self) return fmt("%.4d", self:getisoyear()) end,
-- same as %b
['%h']=function(self) return self:fmt0("%b") end,
-- hour of the 24-hour day, from 00 (06)
['%H']=function(self) return fmt("%.2d", self:gethours()) end,
-- The hour as a number using a 12-hour clock (01 - 12)
['%I']=function(self) return fmt("%.2d", self:getclockhour()) end,
-- The day of the year as a number (001 - 366)
['%j']=function(self) return fmt("%.3d", self:getyearday()) end,
-- Month of the year, from 01 to 12
['%m']=function(self) return fmt("%.2d", self:getmonth()) end,
-- Minutes after the hour 55
['%M']=function(self) return fmt("%.2d", self:getminutes())end,
-- AM/PM indicator (AM)
['%p']=function(self) return sl_meridian[self:gethours() > 11 and 1 or -1] end, --AM/PM indicator (AM)
-- The second as a number (59, 20 , 01)
['%S']=function(self) return fmt("%.2d", self:getseconds()) end,
-- ISO 8601 day of the week, to 7 for Sunday (7, 1)
['%u']=function(self) return self:getisoweekday() end,
-- Sunday week of the year, from 00 (48)
['%U']=function(self) return fmt("%.2d", self:getweeknumber()) end,
-- ISO 8601 week of the year, from 01 (48)
['%V']=function(self) return fmt("%.2d", self:getisoweeknumber()) end,
-- The day of the week as a decimal, Sunday being 0
['%w']=function(self) return self:getweekday() - 1 end,
-- Monday week of the year, from 00 (48)
['%W']=function(self) return fmt("%.2d", self:getweeknumber(2)) end,
-- The year as a number without a century (range 00 to 99)
['%y']=function(self) return fmt("%.2d", mod(self:getyear() ,100)) end,
-- Year with century (2000, 1914, 0325, 0001)
['%Y']=function(self) return fmt("%.4d", self:getyear()) end,
-- Time zone offset, the date object is assumed local time (+1000, -0230)
['%z']=function(self) local b = -self:getbias(); local x = abs(b); return fmt("%s%.4d", b < 0 and "-" or "+", fix(x/60)*100 + floor(mod(x,60))) end,
-- Time zone name, the date object is assumed local time
['%Z']=function(self) return self:gettzname() end,
-- Misc --
-- Year, if year is in BCE, prints the BCE Year representation, otherwise result is similar to "%Y" (1 BCE, 40 BCE)
['%\b']=function(self) local x = self:getyear() return fmt("%.4d%s", x>0 and x or (-x+1), x>0 and "" or " BCE") end,
-- Seconds including fraction (59.998, 01.123)
['%\f']=function(self) local x = self:getfracsec() return fmt("%s%.9f",x >= 10 and "" or "0", x) end,
-- percent character %
['%%']=function(self) return "%" end,
-- Group Spec --
-- 12-hour time, from 01:00:00 AM (06:55:15 AM); same as "%I:%M:%S %p"
['%r']=function(self) return self:fmt0("%I:%M:%S %p") end,
-- hour:minute, from 01:00 (06:55); same as "%I:%M"
['%R']=function(self) return self:fmt0("%I:%M") end,
-- 24-hour time, from 00:00:00 (06:55:15); same as "%H:%M:%S"
['%T']=function(self) return self:fmt0("%H:%M:%S") end,
-- month/day/year from 01/01/00 (12/02/79); same as "%m/%d/%y"
['%D']=function(self) return self:fmt0("%m/%d/%y") end,
-- year-month-day (1979-12-02); same as "%Y-%m-%d"
['%F']=function(self) return self:fmt0("%Y-%m-%d") end,
-- The preferred date and time representation; same as "%x %X"
['%c']=function(self) return self:fmt0("%x %X") end,
-- The preferred date representation, same as "%a %b %d %\b"
['%x']=function(self) return self:fmt0("%a %b %d %\b") end,
-- The preferred time representation, same as "%H:%M:%\f"
['%X']=function(self) return self:fmt0("%H:%M:%\f") end,
-- GroupSpec --
-- Iso format, same as "%Y-%m-%dT%T"
['${iso}'] = function(self) return self:fmt0("%Y-%m-%dT%T") end,
-- http format, same as "%a, %d %b %Y %T GMT"
['${http}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end,
-- ctime format, same as "%a %b %d %T GMT %Y"
['${ctime}'] = function(self) return self:fmt0("%a %b %d %T GMT %Y") end,
-- RFC850 format, same as "%A, %d-%b-%y %T GMT"
['${rfc850}'] = function(self) return self:fmt0("%A, %d-%b-%y %T GMT") end,
-- RFC1123 format, same as "%a, %d %b %Y %T GMT"
['${rfc1123}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end,
-- asctime format, same as "%a %b %d %T %Y"
['${asctime}'] = function(self) return self:fmt0("%a %b %d %T %Y") end,
}
function dobj:fmt0(str) return (gsub(str, "%%[%a%%\b\f]", function(x) local f = tvspec[x];return (f and f(self)) or x end)) end
function dobj:fmt(str)
str = str or self.fmtstr or fmtstr
return self:fmt0((gmatch(str, "${%w+}")) and (gsub(str, "${%w+}", function(x)local f=tvspec[x];return (f and f(self)) or x end)) or str)
end
dobj.format = dobj.fmt
function dobj.__lt(a, b) if (a.daynum == b.daynum) then return (a.dayfrc < b.dayfrc) else return (a.daynum < b.daynum) end end
function dobj.__le(a, b) if (a.daynum == b.daynum) then return (a.dayfrc <= b.dayfrc) else return (a.daynum <= b.daynum) end end
function dobj.__eq(a, b)return (a.daynum == b.daynum) and (a.dayfrc == b.dayfrc) end
function dobj.__sub(a,b)
local d1, d2 = date_getdobj(a), date_getdobj(b)
local d0 = d1 and d2 and date_new(d1.daynum - d2.daynum, d1.dayfrc - d2.dayfrc)
return d0 and d0:normalize()
end
function dobj.__add(a,b)
local d1, d2 = date_getdobj(a), date_getdobj(b)
local d0 = d1 and d2 and date_new(d1.daynum + d2.daynum, d1.dayfrc + d2.dayfrc)
return d0 and d0:normalize()
end
function dobj.__concat(a, b) return tostring(a) .. tostring(b) end
function dobj:__tostring() return self:fmt() end
function dobj:copy() return date_new(self.daynum, self.dayfrc) end
--[[ THE LOCAL DATE OBJECT METHODS ]]--
function dobj:tolocal()
local dn,df = self.daynum, self.dayfrc
local bias = getbiasutc2(self)
if bias then
-- utc = local + bias; local = utc - bias
self.daynum = dn
self.dayfrc = df - bias*TICKSPERSEC
return self:normalize()
else
return nil
end
end
function dobj:toutc()
local dn,df = self.daynum, self.dayfrc
local bias = getbiasloc2(dn, df)
if bias then
-- utc = local + bias;
self.daynum = dn
self.dayfrc = df + bias*TICKSPERSEC
return self:normalize()
else
return nil
end
end
function dobj:getbias() return (getbiasloc2(self.daynum, self.dayfrc))/SECPERMIN end
function dobj:gettzname()
local _, tvu, _ = getbiasloc2(self.daynum, self.dayfrc)
return tvu and osdate("%Z",tvu) or ""
end
--#if not DATE_OBJECT_AFX then
function date.time(h, r, s, t)
h, r, s, t = tonumber(h or 0), tonumber(r or 0), tonumber(s or 0), tonumber(t or 0)
if h and r and s and t then
return date_new(DAYNUM_DEF, makedayfrc(h, r, s, t))
else
return date_error_arg()
end
end
function date:__call(...)
local arg = pack(...)
if arg.n > 1 then return (date_from(...))
elseif arg.n == 0 then return (date_getdobj(false))
else local o, r = date_getdobj(arg[1]); return r and o:copy() or o end
end
date.diff = dobj.__sub
function date.isleapyear(v)
local y = fix(v);
if not y then
y = date_getdobj(v)
y = y and y:getyear()
end
return isleapyear(y+0)
end
function date.epoch() return date_epoch:copy() end
function date.isodate(y,w,d) return date_new(makedaynum_isoywd(y + 0, w and (w+0) or 1, d and (d+0) or 1), 0) end
-- Internal functions
function date.fmt(str) if str then fmtstr = str end; return fmtstr end
function date.daynummin(n) DAYNUM_MIN = (n and n < DAYNUM_MAX) and n or DAYNUM_MIN return n and DAYNUM_MIN or date_new(DAYNUM_MIN, 0):normalize()end
function date.daynummax(n) DAYNUM_MAX = (n and n > DAYNUM_MIN) and n or DAYNUM_MAX return n and DAYNUM_MAX or date_new(DAYNUM_MAX, 0):normalize()end
function date.ticks(t) if t then setticks(t) end return TICKSPERSEC end
--#end -- not DATE_OBJECT_AFX
local tm = osdate("!*t", 0);
if tm then
date_epoch = date_new(makedaynum(tm.year, tm.month - 1, tm.day), makedayfrc(tm.hour, tm.min, tm.sec, 0))
-- the distance from our epoch to os epoch in daynum
DATE_EPOCH = date_epoch and date_epoch:spandays()
else -- error will be raise only if called!
date_epoch = setmetatable({},{__index = function() error("failed to get the epoch date") end})
end
function date.serialize(object)
return {tostring(object.daynum), tostring(object.dayfrc)}
end
function date.construct(object)
return date_isdobj(object) or (object.daynum and date_new(object.daynum, object.dayfrc) or date_new(object[1], object[2]))
end
--#if not DATE_OBJECT_AFX then
return date
--#else
--$return date_from
--#end
================================================
FILE: gamemode/core/libs/thirdparty/sh_middleclass.lua
================================================
local middleclass = {
_VERSION = 'middleclass v4.1.1',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
local function _tostring(self) return "class " .. self.name end
local function _call(self, ...) return self:New(...) end
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }
if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "Included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end
if type(mixin.Included)=="function" then mixin:Included(aClass) end
return aClass
end
local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,
Initialize = function(self, ...) end,
IsInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.IsSubclassOf) == 'function'
and self.class:IsSubclassOf(aClass))
end,
static = {
Allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:Allocate' instead of 'Class.Allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
New = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:New' instead of 'Class.New'")
local instance = self:Allocate()
instance:Initialize(...)
return instance
end,
Subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:Subclass' instead of 'Class.Subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.Initialize = function(instance, ...) return self.Initialize(instance, ...) end
self.subclasses[subclass] = true
self:Subclassed(subclass)
return subclass
end,
Subclassed = function(self, other) end,
IsSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:IsSubclassOf(other) )
end,
Include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:Include' instead of 'Class.Include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}
function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:Subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
ix.middleclass = middleclass
================================================
FILE: gamemode/core/libs/thirdparty/sh_pon.lua
================================================
--[[
DEVELOPMENTAL VERSION;
VERSION 1.2.2
Copyright thelastpenguin™
You may use this for any purpose as long as:
- You don't remove this copyright notice.
- You don't claim this to be your own.
- You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this.
If you modify the code for any purpose, the above still applies to the modified code.
The author is not held responsible for any damages incured from the use of pon, you use it at your own risk.
DATA TYPES SUPPORTED:
- tables - k,v - pointers
- strings - k,v - pointers
- numbers - k,v
- booleans- k,v
- Vectors - k,v
- Angles - k,v
- Entities- k,v
- Players - k,v
CHANGE LOG
V 1.1.0
- Added Vehicle, NPC, NextBot, Player, Weapon
V 1.2.0
- Added custom handling for k,v tables without any array component.
V 1.2.1
- fixed deserialization bug.
THANKS TO...
- VERCAS for the inspiration.
]]
local pon = {};
_G.pon = pon;
local type, count = type, table.Count ;
local tonumber = tonumber ;
local format = string.format;
do
local type, count = type, table.Count ;
local tonumber = tonumber ;
local format = string.format;
local encode = {};
local tryCache ;
local cacheSize = 0;
encode['table'] = function( self, tbl, output, cache )
if( cache[ tbl ] )then
output[ #output + 1 ] = format('(%x)', cache[tbl] );
return ;
else
cacheSize = cacheSize + 1;
cache[ tbl ] = cacheSize;
end
local first = next(tbl, nil)
local predictedNumeric = 1
local lastKey = nil
-- starts with a numeric dealio
if first == 1 then
output[#output + 1] = '{'
for k,v in next, tbl do
if k == predictedNumeric then
predictedNumeric = predictedNumeric + 1
local tv = type(v)
if tv == 'string' then
local pid = cache[v]
if pid then
output[#output + 1] = format('(%x)', pid)
else
cacheSize = cacheSize + 1
cache[v] = cacheSize
self.string(self, v, output, cache)
end
else
self[tv](self, v, output, cache)
end
else
break
end
end
predictedNumeric = predictedNumeric - 1
else
predictedNumeric = nil
end
if predictedNumeric == nil then
output[#output + 1] = '[' -- no array component
else
output[#output + 1] = '~' -- array component came first so shit needs to happen
end
for k, v in next, tbl, predictedNumeric do
local tk, tv = type(k), type(v)
-- WRITE KEY
if tk == 'string' then
local pid = cache[ k ];
if( pid )then
output[ #output + 1 ] = format('(%x)', pid );
else
cacheSize = cacheSize + 1;
cache[ k ] = cacheSize;
self.string( self, k, output, cache );
end
else
self[tk](self, k, output, cache)
end
-- WRITE VALUE
if( tv == 'string' )then
local pid = cache[ v ];
if( pid )then
output[ #output + 1 ] = format('(%x)', pid );
else
cacheSize = cacheSize + 1;
cache[ v ] = cacheSize;
self.string( self, v, output, cache );
end
else
self[ tv ]( self, v, output, cache );
end
end
output[#output + 1] = '}'
end
-- ENCODE STRING
local gsub = string.gsub ;
encode['string'] = function( self, str, output )
--if tryCache( str, output ) then return end
local estr, count = gsub( str, ";", "\\;");
if( count == 0 )then
output[ #output + 1 ] = '\''..str..';';
else
output[ #output + 1 ] = '"'..estr..'";';
end
end
-- ENCODE NUMBER
encode['number'] = function( self, num, output )
if num%1 == 0 then
if num < 0 then
output[ #output + 1 ] = format( 'x%x;', -num );
else
output[ #output + 1 ] = format('X%x;', num );
end
else
output[ #output + 1 ] = tonumber( num )..';';
end
end
-- ENCODE BOOLEAN
encode['boolean'] = function( self, val, output )
output[ #output + 1 ] = val and 't' or 'f'
end
-- ENCODE VECTOR
encode['Vector'] = function( self, val, output )
output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';');
end
-- ENCODE ANGLE
encode['Angle'] = function( self, val, output )
output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';');
end
encode['Entity'] = function( self, val, output )
output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#');
end
encode['Player'] = encode['Entity'];
encode['Vehicle'] = encode['Entity'];
encode['Weapon'] = encode['Entity'];
encode['NPC'] = encode['Entity'];
encode['NextBot'] = encode['Entity'];
encode['PhysObj'] = encode['Entity'];
encode['nil'] = function()
output[ #output + 1 ] = '?';
end
encode.__index = function( key )
ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.');
return encode['nil'];
end
do
local empty, concat = table.Empty, table.concat ;
function pon.encode( tbl )
local output = {};
cacheSize = 0;
encode[ 'table' ]( encode, tbl, output, {} );
local res = concat( output );
return res;
end
end
end
do
local tonumber = tonumber ;
local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ;
local Vector, Angle, Entity = Vector, Angle, Entity ;
local decode = {};
decode['{'] = function( self, index, str, cache )
local cur = {};
cache[ #cache + 1 ] = cur;
local k, v, tk, tv = 1, nil, nil, nil;
while( true )do
tv = sub( str, index, index );
if( not tv or tv == '~' )then
index = index + 1;
break ;
end
if( tv == '}' )then
return index + 1, cur;
end
-- READ THE VALUE
index = index + 1;
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
k = k + 1;
end
while( true )do
tk = sub( str, index, index );
if( not tk or tk == '}' )then
index = index + 1;
break ;
end
-- READ THE KEY
index = index + 1;
index, k = self[ tk ]( self, index, str, cache );
-- READ THE VALUE
tv = sub( str, index, index );
index = index + 1;
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
end
return index, cur;
end
decode['['] = function( self, index, str, cache )
local cur = {};
cache[ #cache + 1 ] = cur;
local k, v, tk, tv = 1, nil, nil, nil;
while( true )do
tk = sub( str, index, index );
if( not tk or tk == '}' )then
index = index + 1;
break ;
end
-- READ THE KEY
index = index + 1;
index, k = self[ tk ]( self, index, str, cache );
if not k then continue end
-- READ THE VALUE
tv = sub( str, index, index );
index = index + 1;
if not self[tv] then
print('did not find type: '..tv)
end
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
end
return index, cur;
end
-- STRING
decode['"'] = function( self, index, str, cache )
local finish = find( str, '";', index, true );
local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' );
index = finish + 2;
cache[ #cache + 1 ] = res;
return index, res;
end
-- STRING NO ESCAPING NEEDED
decode['\''] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local res = sub( str, index, finish - 1 )
index = finish + 1;
cache[ #cache + 1 ] = res;
return index, res;
end
-- NUMBER
decode['n'] = function( self, index, str, cache )
index = index - 1;
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1 ) );
index = finish + 1;
return index, num;
end
decode['0'] = decode['n'];
decode['1'] = decode['n'];
decode['2'] = decode['n'];
decode['3'] = decode['n'];
decode['4'] = decode['n'];
decode['5'] = decode['n'];
decode['6'] = decode['n'];
decode['7'] = decode['n'];
decode['8'] = decode['n'];
decode['9'] = decode['n'];
decode['-'] = decode['n'];
-- positive hex
decode['X'] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, num;
end
-- negative hex
decode['x'] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = -tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, num;
end
-- POINTER
decode['('] = function( self, index, str, cache )
local finish = find( str, ')', index, true );
local num = tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, cache[ num ];
end
-- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO.
decode[ 't' ] = function( self, index )
return index, true;
end
decode[ 'f' ] = function( self, index )
return index, false;
end
-- VECTOR
decode[ 'v' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local vecStr = sub( str, index, finish - 1 );
index = finish + 1; -- update the index.
local segs = Explode( ',', vecStr, false );
return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
end
-- ANGLE
decode[ 'a' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local angStr = sub( str, index, finish - 1 );
index = finish + 1; -- update the index.
local segs = Explode( ',', angStr, false );
return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
end
-- ENTITY
decode[ 'E' ] = function( self, index, str, cache )
if( str[index] == '#' )then
index = index + 1;
return index, NULL ;
else
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1 ) );
index = finish + 1;
return index, Entity( num );
end
end
-- PLAYER
decode[ 'P' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1 ) );
index = finish + 1;
return index, Entity( num ) or NULL;
end
-- NIL
decode['?'] = function( self, index, str, cache )
return index + 1, nil;
end
function pon.decode( data )
local _, res = decode[sub(data,1,1)]( decode, 2, data, {});
return res;
end
end
================================================
FILE: gamemode/core/libs/thirdparty/sh_tween.lua
================================================
local tween = {
_VERSION = 'tween 2.1.1',
_DESCRIPTION = 'tweening for lua',
_URL = 'https://github.com/kikito/tween.lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
-- easing
-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
-- For all easing functions:
-- t = time == how much time has to pass for the tweening to complete
-- b = begin == starting property value
-- c = change == ending - beginning
-- d = duration == running time. How much time has passed *right now*
local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
-- linear
local function linear(t, b, c, d) return c * t / d + b end
-- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
-- cubic
local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
-- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
end
local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
-- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
end
local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
-- sine
local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) -d, b + c / 2, c / 2, d)
end
-- expo
local function inExpo(t, b, c, d)
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
local function outExpo(t, b, c, d)
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end
local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
-- circ
local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
-- elastic
local function calculatePAS(p,a,c,d)
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
end
local function inElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
local function outElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
local function inOutElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
end
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
-- back
local function inBack(t, b, c, d, s)
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
-- bounce
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
tween.easing = {
linear = linear,
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
}
-- private stuff
local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k,v in pairs(keysTable) do
if type(v) == 'table' then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
end
local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {}
local newPath
for k,targetValue in pairs(target) do
newPath = copyTables({}, path)
table.insert(newPath, tostring(k))
if isnumber(targetValue) then
assert(isnumber(subject[k]), "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
elseif istable(targetValue) then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(isnumber(targetValue), "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
end
end
end
local function checkNewParams(duration, subject, target, easing)
assert(isnumber(duration) and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
assert(istable(target), "target must be a table. Was " .. tostring(target))
assert(isfunction(easing), "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
end
local function getEasingFunction(easing)
easing = easing or "linear"
if isstring(easing) then
local name = easing
easing = tween.easing[name]
if not isfunction(easing) then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t,b,c,d
for k,v in pairs(target) do
if istable(v) then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t,b,c,d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t,b,c,d)
end
end
end
local function applyValues(subject, target)
for k, v in pairs(target) do
if (istable(v)) then
applyValues(subject[k], v)
else
subject[k] = v
end
end
end
-- Tween methods
local Tween = {}
local Tween_mt = {__index = Tween}
function Tween:set(clock)
assert(isnumber(clock), "clock must be a positive number or 0")
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
if self.clock <= 0 then
self.clock = 0
applyValues(self.subject, self.initial)
elseif self.clock >= self.duration then -- the tween has expired
self.clock = self.duration
applyValues(self.subject, self.target)
else
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
end
return self.clock >= self.duration
end
function Tween:reset()
return self:set(0)
end
function Tween:update(dt)
assert(isnumber(dt), "dt must be a number")
return self:set(self.clock + dt)
end
-- Public interface
function tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
end
ix.tween = tween
================================================
FILE: gamemode/core/libs/thirdparty/sh_utf8.lua
================================================
-- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $
--
-- Provides UTF-8 aware string functions implemented in pure lua:
-- * string.utf8len(s)
-- * string.utf8sub(s, i, j)
-- * string.utf8reverse(s)
--
-- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these
-- additional functions are available:
-- * string.utf8upper(s)
-- * string.utf8lower(s)
--
-- All functions behave as their non UTF-8 aware counterparts with the exception
-- that UTF-8 characters are used instead of bytes for all units.
--[[
Copyright (c) 2006-2007, Kyle Smith
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--]]
-- ABNF from RFC 3629
--
-- UTF8-octets = *( UTF8-char )
-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
-- UTF8-1 = %x00-7F
-- UTF8-2 = %xC2-DF UTF8-tail
-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
-- %xF4 %x80-8F 2( UTF8-tail )
-- UTF8-tail = %x80-BF
--
ix.util.Include("data/sh_utf8_casemap.lua")
-- returns the number of bytes used by the UTF-8 character at byte i in s
-- also doubles as a UTF-8 character validator
local function utf8charbytes (s, i)
-- argument defaults
i = i or 1
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")")
end
if not isnumber(i) then
error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")")
end
local c = s:byte(i)
-- determine bytes needed for character, based on RFC 3629
-- validate byte 1
if c > 0 and c <= 127 then
-- UTF8-1
return 1
elseif c >= 194 and c <= 223 then
-- UTF8-2
local c2 = s:byte(i + 1)
if not c2 then
error("UTF-8 string terminated early")
end
-- validate byte 2
if c2 < 128 or c2 > 191 then
error("Invalid UTF-8 character")
end
return 2
elseif c >= 224 and c <= 239 then
-- UTF8-3
local c2 = s:byte(i + 1)
local c3 = s:byte(i + 2)
if not c2 or not c3 then
error("UTF-8 string terminated early")
end
-- validate byte 2
if c == 224 and (c2 < 160 or c2 > 191) then
error("Invalid UTF-8 character")
elseif c == 237 and (c2 < 128 or c2 > 159) then
error("Invalid UTF-8 character")
elseif c2 < 128 or c2 > 191 then
error("Invalid UTF-8 character")
end
-- validate byte 3
if c3 < 128 or c3 > 191 then
error("Invalid UTF-8 character")
end
return 3
elseif c >= 240 and c <= 244 then
-- UTF8-4
local c2 = s:byte(i + 1)
local c3 = s:byte(i + 2)
local c4 = s:byte(i + 3)
if not c2 or not c3 or not c4 then
error("UTF-8 string terminated early")
end
-- validate byte 2
if c == 240 and (c2 < 144 or c2 > 191) then
error("Invalid UTF-8 character")
elseif c == 244 and (c2 < 128 or c2 > 143) then
error("Invalid UTF-8 character")
elseif c2 < 128 or c2 > 191 then
error("Invalid UTF-8 character")
end
-- validate byte 3
if c3 < 128 or c3 > 191 then
error("Invalid UTF-8 character")
end
-- validate byte 4
if c4 < 128 or c4 > 191 then
error("Invalid UTF-8 character")
end
return 4
else
error("Invalid UTF-8 character")
end
end
-- returns the number of characters in a UTF-8 string
local function utf8len (s)
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")")
end
local pos = 1
local bytes = s:len()
local len = 0
while pos <= bytes do
len = len + 1
pos = pos + utf8charbytes(s, pos)
end
return len
end
-- install in the string library
if not string.utf8bytes then
string.utf8bytes = utf8charbytes
end
-- install in the string library
if not string.utf8len then
string.utf8len = utf8len
end
-- functions identically to string.sub except that i and j are UTF-8 characters
-- instead of bytes
local function utf8sub (s, i, j)
-- argument defaults
j = j or -1
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8sub' (string expected, got ".. type(s).. ")")
end
if not isnumber(i) then
error("bad argument #2 to 'utf8sub' (number expected, got ".. type(i).. ")")
end
if not isnumber(j) then
error("bad argument #3 to 'utf8sub' (number expected, got ".. type(j).. ")")
end
local pos = 1
local bytes = s:len()
local len = 0
-- only set l if i or j is negative
local l = (i >= 0 and j >= 0) or s:utf8len()
local startChar = (i >= 0) and i or l + i + 1
local endChar = (j >= 0) and j or l + j + 1
-- can't have start before end!
if startChar > endChar then
return ""
end
-- byte offsets to pass to string.sub
local startByte, endByte = 1, bytes
while pos <= bytes do
len = len + 1
if len == startChar then
startByte = pos
end
pos = pos + utf8charbytes(s, pos)
if len == endChar then
endByte = pos - 1
break
end
end
return s:sub(startByte, endByte)
end
-- install in the string library
if not string.utf8sub then
string.utf8sub = utf8sub
end
-- replace UTF-8 characters based on a mapping table
local function utf8replace (s, mapping)
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")")
end
if not istable(mapping) then
error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")")
end
local pos = 1
local bytes = s:len()
local charbytes
local newstr = ""
while pos <= bytes do
charbytes = utf8charbytes(s, pos)
local c = s:sub(pos, pos + charbytes - 1)
newstr = newstr .. (mapping[c] or c)
pos = pos + charbytes
end
return newstr
end
-- identical to string.upper except it knows about unicode simple case conversions
local function utf8upper (s)
return utf8replace(s, utf8_lc_uc)
end
-- install in the string library
if not string.utf8upper and utf8_lc_uc then
string.utf8upper = utf8upper
end
-- identical to string.lower except it knows about unicode simple case conversions
local function utf8lower (s)
return utf8replace(s, utf8_uc_lc)
end
-- install in the string library
if not string.utf8lower and utf8_uc_lc then
string.utf8lower = utf8lower
end
-- identical to string.reverse except that it supports UTF-8
local function utf8reverse (s)
-- argument checking
if not isstring(s) then
error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")")
end
local bytes = s:len()
local pos = bytes
local charbytes
local newstr = ""
while pos > 0 do
c = s:byte(pos)
while c >= 128 and c <= 191 do
pos = pos - 1
c = s:byte(pos)
end
charbytes = utf8charbytes(s, pos)
newstr = newstr .. s:sub(pos, pos + charbytes - 1)
pos = pos - 1
end
return newstr
end
-- install in the string library
if not string.utf8reverse then
string.utf8reverse = utf8reverse
end
================================================
FILE: gamemode/core/libs/thirdparty/sh_yaml.lua
================================================
--[[
(The MIT License)
Copyright (c) 2017 Dominic Letz dominicletz@exosite.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]
local table_print_value
table_print_value = function(value, indent, done)
indent = indent or 0
done = done or {}
if istable(value) and not done [value] then
done [value] = true
local list = {}
for key in pairs (value) do
list[#list + 1] = key
end
table.sort(list, function(a, b) return tostring(a) < tostring(b) end)
local last = list[#list]
local rep = "{\n"
local comma
for _, key in ipairs (list) do
if key == last then
comma = ''
else
comma = ','
end
local keyRep
if isnumber(key) then
keyRep = key
else
keyRep = string.format("%q", tostring(key))
end
rep = rep .. string.format(
"%s[%s] = %s%s\n",
string.rep(" ", indent + 2),
keyRep,
table_print_value(value[key], indent + 2, done),
comma
)
end
rep = rep .. string.rep(" ", indent) -- indent it
rep = rep .. "}"
done[value] = false
return rep
elseif isstring(value) then
return string.format("%q", value)
else
return tostring(value)
end
end
local table_print = function(tt)
print('return '..table_print_value(tt))
end
local table_clone = function(t)
local clone = {}
for k,v in pairs(t) do
clone[k] = v
end
return clone
end
local string_trim = function(s, what)
what = what or " "
return s:gsub("^[" .. what .. "]*(.-)["..what.."]*$", "%1")
end
local push = function(stack, item)
stack[#stack + 1] = item
end
local pop = function(stack)
local item = stack[#stack]
stack[#stack] = nil
return item
end
local context = function (str)
if not isstring(str) then
return ""
end
str = str:sub(0,25):gsub("\n","\\n"):gsub("\"","\\\"");
return ", near \"" .. str .. "\""
end
local Parser = {}
function Parser.new (self, tokens)
self.tokens = tokens
self.parse_stack = {}
self.refs = {}
self.current = 0
return self
end
local exports = {version = "1.2"}
local word = function(w) return "^("..w..")([%s$%c])" end
local tokens = {
{"comment", "^#[^\n]*"},
{"indent", "^\n( *)"},
{"space", "^ +"},
{"true", word("enabled"), const = true, value = true},
{"true", word("true"), const = true, value = true},
{"true", word("yes"), const = true, value = true},
{"true", word("on"), const = true, value = true},
{"false", word("disabled"), const = true, value = false},
{"false", word("false"), const = true, value = false},
{"false", word("no"), const = true, value = false},
{"false", word("off"), const = true, value = false},
{"null", word("null"), const = true, value = nil},
{"null", word("Null"), const = true, value = nil},
{"null", word("NULL"), const = true, value = nil},
{"null", word("~"), const = true, value = nil},
{"id", "^\"([^\"]-)\" *(:[%s%c])"},
{"id", "^'([^']-)' *(:[%s%c])"},
{"string", "^\"([^\"]-)\"", force_text = true},
{"string", "^'([^']-)'", force_text = true},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?)"},
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)"},
{"doc", "^%-%-%-[^%c]*"},
{",", "^,"},
{"string", "^%b{} *[^,%c]+", noinline = true},
{"{", "^{"},
{"}", "^}"},
{"string", "^%b[] *[^,%c]+", noinline = true},
{"[", "^%["},
{"]", "^%]"},
{"-", "^%-"},
{":", "^:"},
{"pipe", "^(|)(%d*[+%-]?)", sep = "\n"},
{"pipe", "^(>)(%d*[+%-]?)", sep = " "},
{"id", "^([%w][%w %-_]*)(:[%s%c])"},
{"string", "^[^%c]+", noinline = true},
{"string", "^[^,%c ]+"}
};
exports.tokenize = function (str)
local token
local row = 0
local ignore
local indents = 0
local lastIndents
local stack = {}
local indentAmount = 0
local inline = false
str = str:gsub("\r\n","\010")
while #str > 0 do
for i in ipairs(tokens) do
local captures = {}
if not inline or tokens[i].noinline == nil then
captures = {str:match(tokens[i][2])}
end
if #captures > 0 then
captures.input = str:sub(0, 25)
token = table_clone(tokens[i])
token[2] = captures
local str2 = str:gsub(tokens[i][2], "", 1)
token.raw = str:sub(1, #str - #str2)
str = str2
if token[1] == "{" or token[1] == "[" then
inline = true
elseif token.const then
-- Since word pattern contains last char we're re-adding it
str = token[2][2] .. str
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
elseif token[1] == "id" then
-- Since id pattern contains last semi-colon we're re-adding it
str = token[2][2] .. str
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
-- Trim
token[2][1] = string_trim(token[2][1])
elseif token[1] == "string" then
-- Finding numbers
local snip = token[2][1]
if not token.force_text then
if snip:match("^(%d+%.%d+)$") or snip:match("^(%d+)$") then
token[1] = "number"
end
end
elseif token[1] == "comment" then
ignore = true;
elseif token[1] == "indent" then
row = row + 1
inline = false
lastIndents = indents
if indentAmount == 0 then
indentAmount = #token[2][1]
end
if indentAmount ~= 0 then
indents = (#token[2][1] / indentAmount);
else
indents = 0
end
if indents == lastIndents then
ignore = true;
elseif indents > lastIndents + 2 then
error("SyntaxError: invalid indentation, got " .. tostring(indents)
.. " instead of " .. tostring(lastIndents) .. context(token[2].input))
elseif indents > lastIndents + 1 then
push(stack, token)
elseif indents < lastIndents then
local input = token[2].input
token = {"dedent", {"", input = ""}}
token.input = input
while lastIndents > indents + 1 do
lastIndents = lastIndents - 1
push(stack, token)
end
end
end -- if token[1] == XXX
token.row = row
break
end -- if #captures > 0
end
if not ignore then
if token then
push(stack, token)
token = nil
else
error("SyntaxError " .. context(str))
end
end
ignore = false;
end
return stack
end
Parser.peek = function (self, offset)
offset = offset or 1
return self.tokens[offset + self.current]
end
Parser.advance = function (self)
self.current = self.current + 1
return self.tokens[self.current]
end
Parser.advanceValue = function (self)
return self:advance()[2][1]
end
Parser.accept = function (self, type)
if self:peekType(type) then
return self:advance()
end
end
Parser.expect = function (self, type, msg)
return self:accept(type) or
error(msg .. context(self:peek()[1].input))
end
Parser.expectDedent = function (self, msg)
return self:accept("dedent") or (self:peek() == nil) or
error(msg .. context(self:peek()[2].input))
end
Parser.peekType = function (self, val, offset)
return self:peek(offset) and self:peek(offset)[1] == val
end
Parser.ignore = function (self, items)
local advanced
repeat
advanced = false
for _,v in pairs(items) do
if self:peekType(v) then
self:advance()
advanced = true
end
end
until advanced == false
end
Parser.ignoreSpace = function (self)
self:ignore{"space"}
end
Parser.ignoreWhitespace = function (self)
self:ignore{"space", "indent", "dedent"}
end
Parser.parse = function (self)
local ref = nil
if self:peekType("string") and not self:peek().force_text then
local char = self:peek()[2][1]:sub(1,1)
if char == "&" then
ref = self:peek()[2][1]:sub(2)
self:advanceValue()
self:ignoreSpace()
elseif char == "*" then
ref = self:peek()[2][1]:sub(2)
return self.refs[ref]
end
end
local result
local c = {
indent = self:accept("indent") and 1 or 0,
token = self:peek()
}
push(self.parse_stack, c)
if c.token[1] == "doc" then
result = self:parseDoc()
elseif c.token[1] == "-" then
result = self:parseList()
elseif c.token[1] == "{" then
result = self:parseInlineHash()
elseif c.token[1] == "[" then
result = self:parseInlineList()
elseif c.token[1] == "id" then
result = self:parseHash()
elseif c.token[1] == "string" then
result = self:parseString("\n")
elseif c.token[1] == "timestamp" then
result = self:parseTimestamp()
elseif c.token[1] == "number" then
result = tonumber(self:advanceValue())
elseif c.token[1] == "pipe" then
result = self:parsePipe()
elseif c.token.const == true then
self:advanceValue();
result = c.token.value
else
error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input))
end
pop(self.parse_stack)
while c.indent > 0 do
c.indent = c.indent - 1
local term = "term "..c.token[1]..": '"..c.token[2][1].."'"
self:expectDedent("last ".. term .." is not properly dedented")
end
if ref then
self.refs[ref] = result
end
return result
end
Parser.parseDoc = function (self)
self:accept("doc")
return self:parse()
end
Parser.inline = function (self)
local current = self:peek(0)
if not current then
return {}, 0
end
local inline = {}
local i = 0
while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do
inline[self:peek(i)[1]] = true
i = i - 1
end
return inline, -i
end
Parser.isInline = function (self)
local _, i = self:inline()
return i > 0
end
Parser.parent = function(self, level)
level = level or 1
return self.parse_stack[#self.parse_stack - level]
end
Parser.parentType = function(self, type, level)
return self:parent(level) and self:parent(level).token[1] == type
end
Parser.parseString = function (self)
if self:isInline() then
local result = self:advanceValue()
--[[
- a: this looks
flowing: but is
no: string
--]]
local types = self:inline()
if types["id"] and types["-"] then
if not self:peekType("indent") or not self:peekType("indent", 2) then
return result
end
end
--[[
a: 1
b: this is
a flowing string
example
c: 3
--]]
if self:peekType("indent") then
self:expect("indent", "text block needs to start with indent")
local addtl = self:accept("indent")
result = result .. "\n" .. self:parseTextBlock("\n")
self:expectDedent("text block ending dedent missing")
if addtl then
self:expectDedent("text block ending dedent missing")
end
end
return result
else
--[[
a: 1
b:
this is also
a flowing string
example
c: 3
--]]
return self:parseTextBlock("\n")
end
end
Parser.parsePipe = function (self)
local pipe = self:expect("pipe")
self:expect("indent", "text block needs to start with indent")
local result = self:parseTextBlock(pipe.sep)
self:expectDedent("text block ending dedent missing")
return result
end
Parser.parseTextBlock = function (self, sep)
local token = self:advance()
local result = string_trim(token.raw, "\n")
local indents = 0
while self:peek() ~= nil and ( indents > 0 or not self:peekType("dedent") ) do
local newtoken = self:advance()
while token.row < newtoken.row do
result = result .. sep
token.row = token.row + 1
end
if newtoken[1] == "indent" then
indents = indents + 1
elseif newtoken[1] == "dedent" then
indents = indents - 1
else
result = result .. string_trim(newtoken.raw, "\n")
end
end
return result
end
Parser.parseHash = function (self, hash)
hash = hash or {}
local indents = 0
if self:isInline() then
local id = self:advanceValue()
self:expect(":", "expected semi-colon after id")
self:ignoreSpace()
if self:accept("indent") then
indents = indents + 1
hash[id] = self:parse()
else
hash[id] = self:parse()
if self:accept("indent") then
indents = indents + 1
end
end
self:ignoreSpace();
end
while self:peekType("id") do
local id = self:advanceValue()
self:expect(":","expected semi-colon after id")
self:ignoreSpace()
hash[id] = self:parse()
self:ignoreSpace();
end
while indents > 0 do
self:expectDedent("expected dedent")
indents = indents - 1
end
return hash
end
Parser.parseInlineHash = function (self)
local id
local hash = {}
local i = 0
self:accept("{")
while not self:accept("}") do
self:ignoreSpace()
if i > 0 then
self:expect(",","expected comma")
end
self:ignoreWhitespace()
if self:peekType("id") then
id = self:advanceValue()
if id then
self:expect(":","expected semi-colon after id")
self:ignoreSpace()
hash[id] = self:parse()
self:ignoreWhitespace()
end
end
i = i + 1
end
return hash
end
Parser.parseList = function (self)
local list = {}
while self:accept("-") do
self:ignoreSpace()
list[#list + 1] = self:parse()
self:ignoreSpace()
end
return list
end
Parser.parseInlineList = function (self)
local list = {}
local i = 0
self:accept("[")
while not self:accept("]") do
self:ignoreSpace()
if i > 0 then
self:expect(",","expected comma")
end
self:ignoreSpace()
list[#list + 1] = self:parse()
self:ignoreSpace()
i = i + 1
end
return list
end
Parser.parseTimestamp = function (self)
local capture = self:advance()[2]
return os.time{
year = capture[1],
month = capture[2],
day = capture[3],
hour = capture[4] or 0,
min = capture[5] or 0,
sec = capture[6] or 0
}
end
exports.Eval = function (str)
return Parser:new(exports.tokenize(str)):parse()
end
exports.Read = function(file_name)
if file.Exists(file_name, 'GAME') then
local local_name = file_name:gsub('%.y([a]?)ml', '.local.y%1ml')
if file.Exists(local_name, 'GAME') then
file_name = local_name
end
return Parser:new(exports.tokenize(file.Read(file_name, 'GAME'))):parse()
end
end
exports.Dump = table_print
ix.yaml = exports
================================================
FILE: gamemode/core/libs/thirdparty/sv_mysql.lua
================================================
--[[
mysql - 1.0.3
A simple MySQL wrapper for Garry's Mod.
Alexander Grist-Hucker
http://www.alexgrist.com
The MIT License (MIT)
Copyright (c) 2014 Alex Grist-Hucker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]]
mysql = mysql or {
module = "sqlite"
}
local QueueTable = {}
local tostring = tostring
local table = table
--[[
Replacement tables
--]]
local Replacements = {
sqlite = {
Create = {
{"UNSIGNED ", ""},
{"NOT NULL AUTO_INCREMENT", ""}, -- assuming primary key
{"AUTO_INCREMENT", ""},
{"INT%(%d*%)", "INTEGER"},
{"INT ", "INTEGER"}
}
}
}
--[[
Phrases
--]]
local MODULE_NOT_EXIST = "[mysql] The %s module does not exist!\n"
--[[
Begin Query Class.
--]]
local QUERY_CLASS = {}
QUERY_CLASS.__index = QUERY_CLASS
function QUERY_CLASS:New(tableName, queryType)
local newObject = setmetatable({}, QUERY_CLASS)
newObject.queryType = queryType
newObject.tableName = tableName
newObject.selectList = {}
newObject.insertList = {}
newObject.updateList = {}
newObject.createList = {}
newObject.whereList = {}
newObject.orderByList = {}
return newObject
end
function QUERY_CLASS:Escape(text)
return mysql:Escape(tostring(text))
end
function QUERY_CLASS:ForTable(tableName)
self.tableName = tableName
end
function QUERY_CLASS:Where(key, value)
self:WhereEqual(key, value)
end
function QUERY_CLASS:WhereEqual(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` = '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereNotEqual(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` != '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereLike(key, value, format)
format = format or "%%%s%%"
self.whereList[#self.whereList + 1] = "`"..key.."` LIKE '"..string.format(format, self:Escape(value)).."'"
end
function QUERY_CLASS:WhereNotLike(key, value, format)
format = format or "%%%s%%"
self.whereList[#self.whereList + 1] = "`"..key.."` NOT LIKE '"..string.format(format, self:Escape(value)).."'"
end
function QUERY_CLASS:WhereGT(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` > '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereLT(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` < '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereGTE(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` >= '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereLTE(key, value)
self.whereList[#self.whereList + 1] = "`"..key.."` <= '"..self:Escape(value).."'"
end
function QUERY_CLASS:WhereIn(key, value)
value = istable(value) and value or {value}
local values = ""
local bFirst = true
for k, v in pairs(value) do
values = values .. (bFirst and "" or ", ") .. "'" .. self:Escape(v) .. "'"
bFirst = false
end
self.whereList[#self.whereList + 1] = "`"..key.."` IN ("..values..")"
end
function QUERY_CLASS:OrderByDesc(key)
self.orderByList[#self.orderByList + 1] = "`"..key.."` DESC"
end
function QUERY_CLASS:OrderByAsc(key)
self.orderByList[#self.orderByList + 1] = "`"..key.."` ASC"
end
function QUERY_CLASS:Callback(queryCallback)
self.callback = queryCallback
end
function QUERY_CLASS:Select(fieldName)
self.selectList[#self.selectList + 1] = "`"..fieldName.."`"
end
function QUERY_CLASS:Insert(key, value)
self.insertList[#self.insertList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"}
end
function QUERY_CLASS:Update(key, value)
self.updateList[#self.updateList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"}
end
function QUERY_CLASS:Create(key, value)
self.createList[#self.createList + 1] = {"`"..key.."`", value}
end
function QUERY_CLASS:Add(key, value)
self.add = {"`"..key.."`", value}
end
function QUERY_CLASS:Drop(key)
self.drop = "`"..key.."`"
end
function QUERY_CLASS:PrimaryKey(key)
self.primaryKey = "`"..key.."`"
end
function QUERY_CLASS:Limit(value)
self.limit = value
end
function QUERY_CLASS:Offset(value)
self.offset = value
end
local function ApplyQueryReplacements(mode, query)
if (!Replacements[mysql.module]) then
return query
end
local result = query
local entries = Replacements[mysql.module][mode]
for i = 1, #entries do
result = string.gsub(result, entries[i][1], entries[i][2])
end
return result
end
local function BuildSelectQuery(queryObj)
local queryString = {"SELECT"}
if (!istable(queryObj.selectList) or #queryObj.selectList == 0) then
queryString[#queryString + 1] = " *"
else
queryString[#queryString + 1] = " "..table.concat(queryObj.selectList, ", ")
end
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " FROM `"..queryObj.tableName.."` "
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
queryString[#queryString + 1] = " WHERE "
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
end
if (istable(queryObj.orderByList) and #queryObj.orderByList > 0) then
queryString[#queryString + 1] = " ORDER BY "
queryString[#queryString + 1] = table.concat(queryObj.orderByList, ", ")
end
if (isnumber(queryObj.limit)) then
queryString[#queryString + 1] = " LIMIT "
queryString[#queryString + 1] = queryObj.limit
end
if (isnumber(queryObj.offset)) then
queryString[#queryString + 1] = " OFFSET "
queryString[#queryString + 1] = queryObj.offset
end
return table.concat(queryString)
end
local function BuildInsertQuery(queryObj, bIgnore)
local suffix = (bIgnore and (mysql.module == "sqlite" and "INSERT OR IGNORE INTO" or "INSERT IGNORE INTO") or "INSERT INTO")
local queryString = {suffix}
local keyList = {}
local valueList = {}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
for i = 1, #queryObj.insertList do
keyList[#keyList + 1] = queryObj.insertList[i][1]
valueList[#valueList + 1] = queryObj.insertList[i][2]
end
if (#keyList == 0) then
return
end
queryString[#queryString + 1] = " ("..table.concat(keyList, ", ")..")"
queryString[#queryString + 1] = " VALUES ("..table.concat(valueList, ", ")..")"
return table.concat(queryString)
end
local function BuildUpdateQuery(queryObj)
local queryString = {"UPDATE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.updateList) and #queryObj.updateList > 0) then
local updateList = {}
queryString[#queryString + 1] = " SET"
for i = 1, #queryObj.updateList do
updateList[#updateList + 1] = queryObj.updateList[i][1].." = "..queryObj.updateList[i][2]
end
queryString[#queryString + 1] = " "..table.concat(updateList, ", ")
end
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
queryString[#queryString + 1] = " WHERE "
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
end
if (isnumber(queryObj.offset)) then
queryString[#queryString + 1] = " OFFSET "
queryString[#queryString + 1] = queryObj.offset
end
return table.concat(queryString)
end
local function BuildDeleteQuery(queryObj)
local queryString = {"DELETE FROM"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
queryString[#queryString + 1] = " WHERE "
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
end
if (isnumber(queryObj.limit)) then
queryString[#queryString + 1] = " LIMIT "
queryString[#queryString + 1] = queryObj.limit
end
return table.concat(queryString)
end
local function BuildDropQuery(queryObj)
local queryString = {"DROP TABLE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
return table.concat(queryString)
end
local function BuildTruncateQuery(queryObj)
local queryString = {"TRUNCATE TABLE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
return table.concat(queryString)
end
local function BuildCreateQuery(queryObj)
local queryString = {"CREATE TABLE IF NOT EXISTS"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
queryString[#queryString + 1] = " ("
if (istable(queryObj.createList) and #queryObj.createList > 0) then
local createList = {}
for i = 1, #queryObj.createList do
if (mysql.module == "sqlite") then
createList[#createList + 1] = queryObj.createList[i][1].." "..ApplyQueryReplacements("Create", queryObj.createList[i][2])
else
createList[#createList + 1] = queryObj.createList[i][1].." "..queryObj.createList[i][2]
end
end
queryString[#queryString + 1] = " "..table.concat(createList, ", ")
end
if (isstring(queryObj.primaryKey)) then
queryString[#queryString + 1] = ", PRIMARY KEY"
queryString[#queryString + 1] = " ("..queryObj.primaryKey..")"
end
queryString[#queryString + 1] = " )"
return table.concat(queryString)
end
local function BuildAlterQuery(queryObj)
local queryString = {"ALTER TABLE"}
if (isstring(queryObj.tableName)) then
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
else
ErrorNoHalt("[mysql] No table name specified!\n")
return
end
if (istable(queryObj.add)) then
queryString[#queryString + 1] = " ADD "..queryObj.add[1].." "..ApplyQueryReplacements("Create", queryObj.add[2])
elseif (isstring(queryObj.drop)) then
if (mysql.module == "sqlite") then
ErrorNoHalt("[mysql] Cannot drop columns in sqlite!\n")
return
end
queryString[#queryString + 1] = " DROP COLUMN "..queryObj.drop
end
return table.concat(queryString)
end
function QUERY_CLASS:Execute(bQueueQuery)
local queryString = nil
local queryType = string.lower(self.queryType)
if (queryType == "select") then
queryString = BuildSelectQuery(self)
elseif (queryType == "insert") then
queryString = BuildInsertQuery(self)
elseif (queryType == "insert ignore") then
queryString = BuildInsertQuery(self, true)
elseif (queryType == "update") then
queryString = BuildUpdateQuery(self)
elseif (queryType == "delete") then
queryString = BuildDeleteQuery(self)
elseif (queryType == "drop") then
queryString = BuildDropQuery(self)
elseif (queryType == "truncate") then
queryString = BuildTruncateQuery(self)
elseif (queryType == "create") then
queryString = BuildCreateQuery(self)
elseif (queryType == "alter") then
queryString = BuildAlterQuery(self)
end
if (isstring(queryString)) then
if (!bQueueQuery) then
return mysql:RawQuery(queryString, self.callback)
else
return mysql:Queue(queryString, self.callback)
end
end
end
--[[
End Query Class.
--]]
function mysql:Select(tableName)
return QUERY_CLASS:New(tableName, "SELECT")
end
function mysql:Insert(tableName)
return QUERY_CLASS:New(tableName, "INSERT")
end
function mysql:InsertIgnore(tableName)
return QUERY_CLASS:New(tableName, "INSERT IGNORE")
end
function mysql:Update(tableName)
return QUERY_CLASS:New(tableName, "UPDATE")
end
function mysql:Delete(tableName)
return QUERY_CLASS:New(tableName, "DELETE")
end
function mysql:Drop(tableName)
return QUERY_CLASS:New(tableName, "DROP")
end
function mysql:Truncate(tableName)
return QUERY_CLASS:New(tableName, "TRUNCATE")
end
function mysql:Create(tableName)
return QUERY_CLASS:New(tableName, "CREATE")
end
function mysql:Alter(tableName)
return QUERY_CLASS:New(tableName, "ALTER")
end
local UTF8MB4 = "ALTER DATABASE %s CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci"
-- A function to connect to the MySQL database.
function mysql:Connect(host, username, password, database, port, socket, flags)
port = port or 3306
if (self.module == "mysqloo") then
if (!istable(mysqloo)) then
require("mysqloo")
end
if (mysqloo) then
if (self.connection and self.connection:ping()) then
return
end
local clientFlag = flags or 0
if (!isstring(socket)) then
self.connection = mysqloo.connect(host, username, password, database, port)
else
self.connection = mysqloo.connect(host, username, password, database, port, socket, clientFlag)
end
self.connection.onConnected = function(connection)
local success, error_message = connection:setCharacterSet("utf8mb4")
if (!success) then
ErrorNoHalt("Failed to set MySQL encoding!\n")
ErrorNoHalt(error_message .. "\n")
else
self:RawQuery(string.format(UTF8MB4, database))
end
mysql:OnConnected()
end
self.connection.onConnectionFailed = function(database, errorText)
mysql:OnConnectionFailed(errorText)
end
self.connection:connect()
timer.Create("mysql.KeepAlive", 300, 0, function()
self.connection:ping()
end)
else
ErrorNoHalt(string.format(MODULE_NOT_EXIST, self.module))
end
elseif (self.module == "sqlite") then
timer.Simple(0, function()
mysql:OnConnected()
end)
end
end
-- A function to query the MySQL database.
function mysql:RawQuery(query, callback, flags, ...)
if (self.module == "mysqloo") then
local queryObj = self.connection:query(query)
queryObj:setOption(mysqloo.OPTION_NAMED_FIELDS)
queryObj.onSuccess = function(queryObj, result)
if (callback) then
local bStatus, value = pcall(callback, result, true, tonumber(queryObj:lastInsert()))
if (!bStatus) then
error(string.format("[mysql] MySQL Callback Error!\n%s\n", value))
end
end
end
queryObj.onError = function(queryObj, errorText)
ErrorNoHalt(string.format("[mysql] MySQL Query Error!\nQuery: %s\n%s\n", query, errorText))
end
queryObj:start()
elseif (self.module == "sqlite") then
local result = sql.Query(query)
if (result == false) then
error(string.format("[mysql] SQL Query Error!\nQuery: %s\n%s\n", query, sql.LastError()))
else
if (callback) then
local bStatus, value = pcall(callback, result, true, tonumber(sql.QueryValue("SELECT last_insert_rowid()")))
if (!bStatus) then
error(string.format("[mysql] SQL Callback Error!\n%s\n", value))
end
end
end
else
ErrorNoHalt(string.format("[mysql] Unsupported module \"%s\"!\n", self.module))
end
end
-- A function to add a query to the queue.
function mysql:Queue(queryString, callback)
if (isstring(queryString)) then
QueueTable[#QueueTable + 1] = {queryString, callback}
end
end
-- A function to escape a string for MySQL.
function mysql:Escape(text)
if (self.connection) then
if (self.module == "mysqloo") then
return self.connection:escape(text)
end
else
return sql.SQLStr(text, true)
end
end
-- A function to disconnect from the MySQL database.
function mysql:Disconnect()
if (self.connection) then
if (self.module == "mysqloo") then
self.connection:disconnect(true)
end
end
end
function mysql:Think()
if (#QueueTable > 0) then
if (istable(QueueTable[1])) then
local queueObj = QueueTable[1]
local queryString = queueObj[1]
local callback = queueObj[2]
if (isstring(queryString)) then
self:RawQuery(queryString, callback)
end
table.remove(QueueTable, 1)
end
end
end
-- A function to set the module that should be used.
function mysql:SetModule(moduleName)
self.module = moduleName
end
-- Called when the database connects sucessfully.
function mysql:OnConnected()
MsgC(Color(25, 235, 25), "[mysql] Connected to the database!\n")
hook.Run("DatabaseConnected")
end
-- Called when the database connection fails.
function mysql:OnConnectionFailed(errorText)
ErrorNoHalt(string.format("[mysql] Unable to connect to the database!\n%s\n", errorText))
hook.Run("DatabaseConnectionFailed", errorText)
end
-- A function to check whether or not the module is connected to a database.
function mysql:IsConnected()
return self.module == "mysqloo" and (self.connection and self.connection:ping()) or self.module == "sqlite"
end
return mysql
================================================
FILE: gamemode/core/meta/sh_character.lua
================================================
--[[--
Contains information about a player's current game state.
Characters are a fundamental object type in Helix. They are distinct from players, where players are the representation of a
person's existence in the server that owns a character, and their character is their currently selected persona. All the
characters that a player owns will be loaded into memory once they connect to the server. Characters are saved during a regular
interval, and during specific events (e.g when the owning player switches away from one character to another).
They contain all information that is not persistent with the player; names, descriptions, model, currency, etc. For the most
part, you'll want to keep all information stored on the character since it will probably be different or change if the
player switches to another character. An easy way to do this is to use `ix.char.RegisterVar` to easily create accessor functions
for variables that automatically save to the character object.
]]
-- @classmod Character
local CHAR = ix.meta.character or {}
CHAR.__index = CHAR
CHAR.id = CHAR.id or 0
CHAR.vars = CHAR.vars or {}
-- @todo not this
if (!ix.db) then
ix.util.Include("../libs/sv_database.lua")
end
--- Returns a string representation of this character
-- @realm shared
-- @treturn string String representation
-- @usage print(ix.char.loaded[1])
-- > "character[1]"
function CHAR:__tostring()
return "character["..(self.id or 0).."]"
end
--- Returns true if this character is equal to another character. Internally, this checks character IDs.
-- @realm shared
-- @char other Character to compare to
-- @treturn bool Whether or not this character is equal to the given character
-- @usage print(ix.char.loaded[1] == ix.char.loaded[2])
-- > false
function CHAR:__eq(other)
return self:GetID() == other:GetID()
end
--- Returns this character's database ID. This is guaranteed to be unique.
-- @realm shared
-- @treturn number Unique ID of character
function CHAR:GetID()
return self.id
end
if (SERVER) then
--- Saves this character's info to the database.
-- @realm server
-- @func[opt=nil] callback Function to call when the save has completed.
-- @usage ix.char.loaded[1]:Save(function()
-- print("done!")
-- end)
-- > done! -- after a moment
function CHAR:Save(callback)
-- Do not save if the character is for a bot.
if (self.isBot) then
return
end
-- Let plugins/schema determine if the character should be saved.
local shouldSave = hook.Run("CharacterPreSave", self)
if (shouldSave != false) then
-- Run a query to save the character to the database.
local query = mysql:Update("ix_characters")
-- update all character vars
for k, v in pairs(ix.char.vars) do
if (v.field and self.vars[k] != nil and !v.bSaveLoadInitialOnly) then
local value = self.vars[k]
query:Update(v.field, istable(value) and util.TableToJSON(value) or tostring(value))
end
end
query:Where("id", self:GetID())
query:Callback(function()
if (callback) then
callback()
end
hook.Run("CharacterPostSave", self)
end)
query:Execute()
end
end
--- Networks this character's information to make the given player aware of this character's existence. If the receiver is
-- not the owner of this character, it will only be sent a limited amount of data (as it does not need anything else).
-- This is done automatically by the framework.
-- @internal
-- @realm server
-- @player[opt=nil] receiver Player to send the information to. This will sync to all connected players if set to `nil`.
function CHAR:Sync(receiver)
-- Broadcast the character information if receiver is not set.
if (receiver == nil) then
for _, v in player.Iterator() do
self:Sync(v)
end
-- Send all character information if the receiver is the character's owner.
elseif (receiver == self.player) then
local data = {}
for k, v in pairs(self.vars) do
if (ix.char.vars[k] != nil and !ix.char.vars[k].bNoNetworking) then
data[k] = v
end
end
net.Start("ixCharacterInfo")
net.WriteTable(data)
net.WriteUInt(self:GetID(), 32)
net.WriteUInt(self.player:EntIndex(), 8)
net.Send(self.player)
else
local data = {}
for k, v in pairs(ix.char.vars) do
if (!v.bNoNetworking and !v.isLocal) then
data[k] = self.vars[k]
end
end
net.Start("ixCharacterInfo")
net.WriteTable(data)
net.WriteUInt(self:GetID(), 32)
net.WriteUInt(self.player:EntIndex(), 8)
net.Send(receiver)
end
end
-- Sets up the "appearance" related inforomation for the character.
--- Applies the character's appearance and synchronizes information to the owning player.
-- @realm server
-- @internal
-- @bool[opt] bNoNetworking Whether or not to sync the character info to other players
function CHAR:Setup(bNoNetworking)
local client = self:GetPlayer()
if (IsValid(client)) then
-- Set the faction, model, and character index for the player.
local model = self:GetModel()
client:SetNetVar("char", self:GetID())
client:SetTeam(self:GetFaction())
client:SetModel(istable(model) and model[1] or model)
-- Apply saved body groups.
for k, v in pairs(self:GetData("groups", {})) do
client:SetBodygroup(k, v)
end
-- Apply a saved skin.
client:SetSkin(self:GetData("skin", 0))
-- Synchronize the character if we should.
if (!bNoNetworking) then
if (client:IsBot()) then
timer.Simple(0.33, function()
self:Sync()
end)
else
self:Sync()
end
for _, v in ipairs(self:GetInventory(true)) do
if (istable(v)) then
v:AddReceiver(client)
v:Sync(client)
end
end
end
local id = self:GetID()
hook.Run("CharacterLoaded", ix.char.loaded[id])
net.Start("ixCharacterLoaded")
net.WriteUInt(id, 32)
net.Send(client)
self.firstTimeLoaded = true
end
end
--- Forces a player off their current character, and sends them to the character menu to select a character.
-- @realm server
function CHAR:Kick()
-- Kill the player so they are not standing anywhere.
local client = self:GetPlayer()
client:KillSilent()
local steamID = client:SteamID64()
local id = self:GetID()
local isCurrentChar = self and self:GetID() == id
-- Return the player to the character menu.
if (self and self.steamID == steamID) then
net.Start("ixCharacterKick")
net.WriteBool(isCurrentChar)
net.Send(client)
if (isCurrentChar) then
client:SetNetVar("char", nil)
client:Spawn()
end
end
end
--- Forces a player off their current character, and prevents them from using the character for the specified amount of time.
-- @realm server
-- @number[opt] time Amount of seconds to ban the character for. If left as `nil`, the character will be banned permanently
function CHAR:Ban(time)
time = tonumber(time)
if (time) then
-- If time is provided, adjust it so it becomes the un-ban time.
time = os.time() + math.max(math.ceil(time), 60)
end
-- Mark the character as banned and kick the character back to menu.
self:SetData("banned", time or true)
self:Kick()
end
end
--- Returns the player that owns this character.
-- @realm shared
-- @treturn player Player that owns this character
function CHAR:GetPlayer()
-- Set the player from entity index.
if (isnumber(self.player)) then
local client = Entity(self.player)
if (IsValid(client)) then
self.player = client
return client
end
-- Return the player from cache.
elseif (IsValid(self.player)) then
return self.player
-- Search for which player owns this character.
elseif (self.steamID) then
local steamID = self.steamID
for _, v in player.Iterator() do
if (v:SteamID64() == steamID) then
self.player = v
return v
end
end
end
end
-- Sets up a new character variable.
function ix.char.RegisterVar(key, data)
-- Store information for the variable.
ix.char.vars[key] = data
data.index = data.index or table.Count(ix.char.vars)
local upperName = key:sub(1, 1):upper() .. key:sub(2)
if (SERVER) then
if (data.field) then
ix.db.AddToSchema("ix_characters", data.field, data.fieldType or ix.type.string)
end
-- Provide functions to change the variable if allowed.
if (!data.bNotModifiable) then
-- Overwrite the set function if desired.
if (data.OnSet) then
CHAR["Set"..upperName] = data.OnSet
-- Have the set function only set on the server if no networking.
elseif (data.bNoNetworking) then
CHAR["Set"..upperName] = function(self, value)
self.vars[key] = value
end
-- If the variable is a local one, only send the variable to the local player.
elseif (data.isLocal) then
CHAR["Set"..upperName] = function(self, value)
local oldVar = self.vars[key]
self.vars[key] = value
net.Start("ixCharacterVarChanged")
net.WriteUInt(self:GetID(), 32)
net.WriteString(key)
net.WriteType(value)
net.Send(self.player)
hook.Run("CharacterVarChanged", self, key, oldVar, value)
end
-- Otherwise network the variable to everyone.
else
CHAR["Set"..upperName] = function(self, value)
local oldVar = self.vars[key]
self.vars[key] = value
net.Start("ixCharacterVarChanged")
net.WriteUInt(self:GetID(), 32)
net.WriteString(key)
net.WriteType(value)
net.Broadcast()
hook.Run("CharacterVarChanged", self, key, oldVar, value)
end
end
end
end
-- The get functions are shared.
-- Overwrite the get function if desired.
if (data.OnGet) then
CHAR["Get"..upperName] = data.OnGet
-- Otherwise return the character variable or default if it does not exist.
else
CHAR["Get"..upperName] = function(self, default)
local value = self.vars[key]
if (value != nil) then
return value
end
if (default == nil) then
return ix.char.vars[key] and (istable(ix.char.vars[key].default) and table.Copy(ix.char.vars[key].default)
or ix.char.vars[key].default)
end
return default
end
end
local alias = data.alias
if (alias) then
if (istable(alias)) then
for _, v in ipairs(alias) do
local aliasName = v:sub(1, 1):upper()..v:sub(2)
CHAR["Get"..aliasName] = CHAR["Get"..upperName]
CHAR["Set"..aliasName] = CHAR["Set"..upperName]
end
elseif (isstring(alias)) then
local aliasName = alias:sub(1, 1):upper()..alias:sub(2)
CHAR["Get"..aliasName] = CHAR["Get"..upperName]
CHAR["Set"..aliasName] = CHAR["Set"..upperName]
end
end
-- Add the variable default to the character object.
CHAR.vars[key] = data.default
end
-- Allows access to the character metatable using ix.meta.character
ix.meta.character = CHAR
================================================
FILE: gamemode/core/meta/sh_entity.lua
================================================
--[[--
Physical object in the game world.
Entities are physical representations of objects in the game world. Helix extends the functionality of entities to interface
between Helix's own classes, and to reduce boilerplate code.
See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Entity) for all other methods that the `Player` class has.
]]
-- @classmod Entity
local meta = FindMetaTable("Entity")
local CHAIR_CACHE = {}
-- Add chair models to the cache by checking if its vehicle category is a class.
for _, v in pairs(list.Get("Vehicles")) do
if (v.Category == "Chairs") then
CHAIR_CACHE[v.Model] = true
end
end
--- Returns `true` if this entity is a chair.
-- @realm shared
-- @treturn bool Whether or not this entity is a chair
function meta:IsChair()
return CHAIR_CACHE[self:GetModel()]
end
--- Returns `true` if this entity is a door. Internally, this checks to see if the entity's class has `door` in its name.
-- @realm shared
-- @treturn bool Whether or not the entity is a door
function meta:IsDoor()
local class = self:GetClass()
return (class and class:find("door") != nil)
end
if (SERVER) then
--- Returns `true` if the given entity is a button or door and is locked.
-- @realm server
-- @treturn bool Whether or not this entity is locked; `false` if this entity cannot be locked at all
-- (e.g not a button or door)
function meta:IsLocked()
if (self:IsVehicle()) then
return self:GetInternalVariable( "VehicleLocked" )
end
return self:GetInternalVariable( "m_bLocked" )
end
--- Returns the neighbouring door entity for double doors.
-- @realm shared
-- @treturn[1] Entity This door's partner
-- @treturn[2] nil If the door does not have a partner
function meta:GetDoorPartner()
return self.ixPartner
end
--- Returns the entity that is blocking this door from opening.
-- @realm server
-- @treturn[1] Entity Entity that is blocking this door
-- @treturn[2] nil If this entity is not a door, or there is no blocking entity
function meta:GetBlocker()
return self:GetInternalVariable( "pBlocker" )
end
--- Blasts a door off its hinges. Internally, this hides the door entity, spawns a physics prop with the same model, and
-- applies force to the prop.
-- @realm server
-- @vector velocity Velocity to apply to the door
-- @number lifeTime How long to wait in seconds before the door is put back on its hinges
-- @bool bIgnorePartner Whether or not to ignore the door's partner in the case of double doors
-- @treturn[1] Entity The physics prop created for the door
-- @treturn nil If the entity is not a door
function meta:BlastDoor(velocity, lifeTime, bIgnorePartner)
if (!self:IsDoor()) then
return
end
if (IsValid(self.ixDummy)) then
self.ixDummy:Remove()
end
velocity = velocity or VectorRand()*100
lifeTime = lifeTime or 120
local partner = self:GetDoorPartner()
if (IsValid(partner) and !bIgnorePartner) then
partner:BlastDoor(velocity, lifeTime, true)
end
local color = self:GetColor()
local dummy = ents.Create("prop_physics")
dummy:SetModel(self:GetModel())
dummy:SetPos(self:GetPos())
dummy:SetAngles(self:GetAngles())
dummy:Spawn()
dummy:SetColor(color)
dummy:SetMaterial(self:GetMaterial())
dummy:SetSkin(self:GetSkin() or 0)
dummy:SetRenderMode(RENDERMODE_TRANSALPHA)
dummy:CallOnRemove("restoreDoor", function()
if (IsValid(self)) then
self:SetNotSolid(false)
self:SetNoDraw(false)
self:DrawShadow(true)
self.ignoreUse = false
self.ixIsMuted = false
for _, v in ents.Iterator() do
if (v:GetParent() == self) then
v:SetNotSolid(false)
v:SetNoDraw(false)
if (v.OnDoorRestored) then
v:OnDoorRestored(self)
end
end
end
end
end)
dummy:SetOwner(self)
dummy:SetCollisionGroup(COLLISION_GROUP_WEAPON)
self:Fire("unlock")
self:Fire("open")
self:SetNotSolid(true)
self:SetNoDraw(true)
self:DrawShadow(false)
self.ignoreUse = true
self.ixDummy = dummy
self.ixIsMuted = true
self:DeleteOnRemove(dummy)
for _, v in ipairs(self:GetBodyGroups() or {}) do
dummy:SetBodygroup(v.id, self:GetBodygroup(v.id))
end
for _, v in ents.Iterator() do
if (v:GetParent() == self) then
v:SetNotSolid(true)
v:SetNoDraw(true)
if (v.OnDoorBlasted) then
v:OnDoorBlasted(self)
end
end
end
dummy:GetPhysicsObject():SetVelocity(velocity)
local uniqueID = "doorRestore"..self:EntIndex()
local uniqueID2 = "doorOpener"..self:EntIndex()
timer.Create(uniqueID2, 1, 0, function()
if (IsValid(self) and IsValid(self.ixDummy)) then
self:Fire("open")
else
timer.Remove(uniqueID2)
end
end)
timer.Create(uniqueID, lifeTime, 1, function()
if (IsValid(self) and IsValid(dummy)) then
uniqueID = "dummyFade"..dummy:EntIndex()
local alpha = 255
timer.Create(uniqueID, 0.1, 255, function()
if (IsValid(dummy)) then
alpha = alpha - 1
dummy:SetColor(ColorAlpha(color, alpha))
if (alpha <= 0) then
dummy:Remove()
end
else
timer.Remove(uniqueID)
end
end)
end
end)
return dummy
end
else
-- Returns the door's slave entity.
function meta:GetDoorPartner()
local owner = self:GetOwner() or self.ixDoorOwner
if (IsValid(owner) and owner:IsDoor()) then
return owner
end
for _, v in ipairs(ents.FindByClass("prop_door_rotating")) do
if (v:GetOwner() == self) then
self.ixDoorOwner = v
return v
end
end
end
end
================================================
FILE: gamemode/core/meta/sh_inventory.lua
================================================
--[[--
Holds items within a grid layout.
Inventories are an object that contains `Item`s in a grid layout. Every `Character` will have exactly one inventory attached to
it, which is the only inventory that is allowed to hold bags - any item that has its own inventory (i.e a suitcase). Inventories
can be owned by a character, or it can be individually interacted with as a standalone object. For example, the container plugin
attaches inventories to props, allowing for items to be stored outside of any character inventories and remain "in the world".
You may be looking for the following common functions:
`Add` Which adds an item to the inventory.
`GetItems` Which gets all of the items inside the inventory.
`GetItemByID` Which gets an item in the inventory by it's item ID.
`GetItemAt` Which gets an item in the inventory by it's x and y
`GetID` Which gets the inventory's ID.
`HasItem` Which checks if the inventory has an item.
]]
-- @classmod Inventory
local META = ix.meta.inventory or ix.middleclass("ix_inventory")
META.slots = META.slots or {}
META.w = META.w or 4
META.h = META.h or 4
META.vars = META.vars or {}
META.receivers = META.receivers or {}
--- Returns a string representation of this inventory
-- @realm shared
-- @treturn string String representation
-- @usage print(ix.item.inventories[1])
-- > "inventory[1]"
function META:__tostring()
return "inventory["..(self.id or 0).."]"
end
--- Initializes the inventory with the provided arguments.
-- @realm shared
-- @internal
-- @number id The `Inventory`'s database ID.
-- @number width The inventory's width.
-- @number height The inventory's height.
function META:Initialize(id, width, height)
self.id = id
self.w = width
self.h = height
self.slots = {}
self.vars = {}
self.receivers = {}
end
--- Returns this inventory's database ID. This is guaranteed to be unique.
-- @realm shared
-- @treturn number Unique ID of inventory
function META:GetID()
return self.id or 0
end
--- Sets the grid size of this inventory.
-- @realm shared
-- @internal
-- @number width New width of inventory
-- @number height New height of inventory
function META:SetSize(width, height)
self.w = width
self.h = height
end
--- Returns the grid size of this inventory.
-- @realm shared
-- @treturn number Width of inventory
-- @treturn number Height of inventory
function META:GetSize()
return self.w, self.h
end
-- this is pretty good to debug/develop function to use.
function META:Print(printPos)
for k, v in pairs(self:GetItems()) do
local str = k .. ": " .. v.name
if (printPos) then
str = str .. " (" .. v.gridX .. ", " .. v.gridY .. ")"
end
print(str)
end
end
--- Searches the inventory to find any stacked items.
-- A common problem with developing, is that items will sometimes error out, or get corrupt.
-- Sometimes, the server knows things you don't while developing live
-- This function can be helpful for getting rid of those pesky errors.
-- @realm shared
function META:FindError()
for k, _ in self:Iter() do
if (k.width == 1 and k.height == 1) then
continue
end
print("Finding error: " .. k.name)
print("Item Position: " .. k.gridX, k.gridY)
for x = k.gridX, k.gridX + k.width - 1 do
for y = k.gridY, k.gridY + k.height - 1 do
local item = self.slots[x][y]
if (item and item.id != k.id) then
print("Error Found: ".. item.name)
end
end
end
end
end
--- Prints out the id, width, height, slots and each item in each slot of an `Inventory`, used for debugging.
-- @realm shared
function META:PrintAll()
print("------------------------")
print("INVID", self:GetID())
print("INVSIZE", self:GetSize())
if (self.slots) then
for x = 1, self.w do
for y = 1, self.h do
local item = self.slots[x] and self.slots[x][y]
if (item and item.id) then
print(item.name .. "(" .. item.id .. ")", x, y)
end
end
end
end
print("INVVARS")
PrintTable(self.vars or {})
print("------------------------")
end
--- Returns the player that owns this inventory.
-- @realm shared
-- @treturn[1] Player Owning player
-- @treturn[2] nil If no connected player owns this inventory
function META:GetOwner()
for _, v in player.Iterator() do
if (v:GetCharacter() and v:GetCharacter().id == self.owner) then
return v
end
end
end
--- Sets the player that owns this inventory.
-- @realm shared
-- @player owner The player to take control over the inventory.
-- @bool fullUpdate Whether or not to update the inventory immediately to the new owner.
function META:SetOwner(owner, fullUpdate)
if (type(owner) == "Player" and owner:GetNetVar("char")) then
owner = owner:GetNetVar("char")
elseif (!isnumber(owner)) then
return
end
if (SERVER) then
if (fullUpdate) then
for _, v in player.Iterator() do
if (v:GetNetVar("char") == owner) then
self:Sync(v, true)
break
end
end
end
local query = mysql:Update("ix_inventories")
query:Update("character_id", owner)
query:Where("inventory_id", self:GetID())
query:Execute()
end
self.owner = owner
end
--- Checks whether a player has access to an inventory
-- @realm shared
-- @internal
-- @player client Player to check access for
-- @treturn bool Whether or not the player has access to the inventory
function META:OnCheckAccess(client)
local bAccess = false
for _, v in ipairs(self:GetReceivers()) do
if (v == client) then
bAccess = true
break
end
end
return bAccess
end
--- Checks whether or not an `Item` can fit into the `Inventory` starting from `x` and `y`.
-- Internally used by FindEmptySlot, in most cases you are better off using that.
-- This function will search if all of the slots within `x + width` and `y + width` are empty,
-- ignoring any space the `Item` itself already occupies.
-- @realm shared
-- @internal
-- @number x The beginning x coordinate to search for.
-- @number y The beginning y coordiate to search for.
-- @number w The `Item`'s width.
-- @number h The `Item`'s height.
-- @item[opt=nil] item2 An `Item`, if any, to ignore when searching.
function META:CanItemFit(x, y, w, h, item2)
local canFit = true
for x2 = 0, w - 1 do
for y2 = 0, h - 1 do
local item = (self.slots[x + x2] or {})[y + y2]
if ((x + x2) > self.w or item) then
if (item2) then
if (item and item.id == item2.id) then
continue
end
end
canFit = false
break
end
end
if (!canFit) then
break
end
end
return canFit
end
--- Returns the amount of slots currently filled in the Inventory.
-- @realm shared
-- @treturn number The amount of slots currently filled.
function META:GetFilledSlotCount()
local count = 0
for x = 1, self.w do
for y = 1, self.h do
if ((self.slots[x] or {})[y]) then
count = count + 1
end
end
end
return count
end
--- Finds an empty slot of a specified width and height.
-- In most cases, to check if an `Item` can actually fit in the `Inventory`,
-- as if it can't, it will just return `nil`.
--
-- FindEmptySlot will loop through all the slots for you, as opposed to `CanItemFit`
-- which you specify an `x` and `y` for.
-- this will call CanItemFit anyway.
-- If you need to check if an item will fit *exactly* at a position, you want CanItemFit instead.
-- @realm shared
-- @number w The width of the `Item` you are trying to fit.
-- @number h The height of the `Item` you are trying to fit.
-- @bool onlyMain Whether or not to search any bags connected to this `Inventory`
-- @treturn[1] number x The `x` coordinate that the `Item` can fit into.
-- @treturn[1] number y The `y` coordinate that the `Item` can fit into.
-- @treturn[2] number x The `x` coordinate that the `Item` can fit into.
-- @treturn[2] number y The `y` coordinate that the `Item` can fit into.
-- @treturn[2] Inventory bagInv If the item was in a bag, it will return the inventory it was in.
-- @see CanItemFit
function META:FindEmptySlot(w, h, onlyMain)
w = w or 1
h = h or 1
if (w > self.w or h > self.h) then
return
end
for y = 1, self.h - (h - 1) do
for x = 1, self.w - (w - 1) do
if (self:CanItemFit(x, y, w, h)) then
return x, y
end
end
end
if (onlyMain != true) then
local bags = self:GetBags()
if (#bags > 0) then
for _, invID in ipairs(bags) do
local bagInv = ix.item.inventories[invID]
if (bagInv) then
local x, y = bagInv:FindEmptySlot(w, h)
if (x and y) then
return x, y, bagInv
end
end
end
end
end
end
--- Returns the item that currently exists within `x` and `y` in the `Inventory`.
-- Items that have a width or height greater than 0 occupy more than 1 x and y.
-- @realm shared
-- @number x The `x` coordindate to search in.
-- @number y The `y` coordinate to search in.
-- @treturn number x The `x` coordinate that the `Item` is located at.
-- @treturn number y The `y` coordinate that the `Item` is located at.
function META:GetItemAt(x, y)
if (self.slots and self.slots[x]) then
return self.slots[x][y]
end
end
--- Removes an item from the inventory.
-- @realm shared
-- @number id The item instance ID to remove
-- @bool[opt=false] bNoReplication Whether or not the item's removal should not be replicated
-- @bool[opt=false] bNoDelete Whether or not the item should not be fully deleted
-- @bool[opt=false] bTransferring Whether or not the item is being transferred to another inventory
-- @treturn number The X position that the item was removed from
-- @treturn number The Y position that the item was removed from
function META:Remove(id, bNoReplication, bNoDelete, bTransferring)
local x2, y2
for x = 1, self.w do
if (self.slots[x]) then
for y = 1, self.h do
local item = self.slots[x][y]
if (item and item.id == id) then
self.slots[x][y] = nil
x2 = x2 or x
y2 = y2 or y
end
end
end
end
if (SERVER and !bNoReplication) then
local receivers = self:GetReceivers()
if (istable(receivers)) then
net.Start("ixInventoryRemove")
net.WriteUInt(id, 32)
net.WriteUInt(self:GetID(), 32)
net.Send(receivers)
end
-- we aren't removing the item - we're transferring it to another inventory
if (!bTransferring) then
hook.Run("InventoryItemRemoved", self, ix.item.instances[id])
end
if (!bNoDelete) then
local item = ix.item.instances[id]
if (item and item.OnRemoved) then
item:OnRemoved()
end
local query = mysql:Delete("ix_items")
query:Where("item_id", id)
query:Execute()
ix.item.instances[id] = nil
end
end
return x2, y2
end
--- Adds a player as a receiver on this `Inventory`
-- Receivers are players who will be networked the items inside the inventory.
--
-- Calling this will *not* automatically sync it's current contents to the client.
-- All future contents will be synced, but not anything that was not synced before this is called.
--
-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error.
-- @realm shared
-- @player client The player to add as a receiver.
function META:AddReceiver(client)
self.receivers[client] = true
end
--- The opposite of `AddReceiver`.
-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error.
-- @realm shared
-- @player client The player to remove from the receiver list.
function META:RemoveReceiver(client)
self.receivers[client] = nil
end
--- Get all of the receivers this `Inventory` has.
-- Receivers are players who will be networked the items inside the inventory.
--
-- This function will automatically sort out invalid players for you.
-- @realm shared
-- @treturn table result The players who are on the server and allowed to see this table.
function META:GetReceivers()
local result = {}
if (self.receivers) then
for k, _ in pairs(self.receivers) do
if (IsValid(k) and k:IsPlayer()) then
result[#result + 1] = k
end
end
end
return result
end
--- Returns a count of a *specific* `Item`s in the `Inventory`
-- @realm shared
-- @string uniqueID The Unique ID of the item.
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
-- @treturn number The amount of `Item`s this inventory has.
-- @usage local curHighest, winner = 0, false
-- for client, character in ix.util.GetCharacters() do
-- local itemCount = character:GetInventory():GetItemCount('water', false)
-- if itemCount > curHighest then
-- curHighest = itemCount
-- winner = character
-- end
-- end
-- -- Finds the thirstiest character on the server and returns their Character ID or false if no character has water.
function META:GetItemCount(uniqueID, onlyMain)
local i = 0
for _, v in pairs(self:GetItems(onlyMain)) do
if (v.uniqueID == uniqueID) then
i = i + 1
end
end
return i
end
--- Returns a table of all `Item`s in the `Inventory` by their Unique ID.
-- Not to be confused with `GetItemsByID` or `GetItemByID` which take in an Item Instance's ID instead.
-- @realm shared
-- @string uniqueID The Unique ID of the item.
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
-- @treturn number The table of specified `Item`s this inventory has.
function META:GetItemsByUniqueID(uniqueID, onlyMain)
local items = {}
for _, v in pairs(self:GetItems(onlyMain)) do
if (v.uniqueID == uniqueID) then
items[#items + 1] = v
end
end
return items
end
--- Returns a table of `Item`s by their base.
-- @realm shared
-- @string baseID The base to search for.
-- @bool bOnlyMain Whether or not to exclude bags that are present from the search.
function META:GetItemsByBase(baseID, bOnlyMain)
local items = {}
for _, v in pairs(self:GetItems(bOnlyMain)) do
if (v.base == baseID) then
items[#items + 1] = v
end
end
return items
end
--- Get an item by it's specific Database ID.
-- @realm shared
-- @number id The ID to search for.
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
-- @treturn item The item if it exists.
function META:GetItemByID(id, onlyMain)
for _, v in pairs(self:GetItems(onlyMain)) do
if (v.id == id) then
return v
end
end
end
--- Get a table of `Item`s by their specific Database ID.
-- It's important to note that while in 99% of cases,
-- items will have a unique Database ID, developers or random GMod weirdness could
-- cause a second item with the same ID to appear, even though, `ix.item.instances` will only store one of those.
-- The inventory only stores a reference to the `ix.item.instance` ID, not the memory reference itself.
-- @realm shared
-- @number id The ID to search for.
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
-- @treturn item The item if it exists.
function META:GetItemsByID(id, onlyMain)
local items = {}
for _, v in pairs(self:GetItems(onlyMain)) do
if (v.id == id) then
items[#items + 1] = v
end
end
return items
end
-- This function may pretty heavy.
--- Returns a table of all the items that an `Inventory` has.
-- @realm shared
-- @bool onlyMain Whether or not to exclude bags from this search.
-- @treturn table The items this `Inventory` has.
function META:GetItems(onlyMain)
local items = {}
for _, v in pairs(self.slots) do
for _, v2 in pairs(v) do
if (istable(v2) and !items[v2.id]) then
items[v2.id] = v2
v2.data = v2.data or {}
local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id)
if (isBag and isBag != self:GetID() and onlyMain != true) then
local bagInv = ix.item.inventories[isBag]
if (bagInv) then
local bagItems = bagInv:GetItems()
table.Merge(items, bagItems)
end
end
end
end
end
return items
end
--- Returns an iterator that returns all contained items, a better way to iterate items than `pairs(inventory:GetItems())`
-- @realm shared
-- @treturn function iterator
function META:Iter()
local x, y, item = 1, 1
return function()
item = nil
repeat
if (x > self.w) then
x, y = 1, y + 1
if (y > self.h) then return nil end
end
item = self.slots[x] and self.slots[x][y]
x = x + 1
until item and not isbool(item) -- skip reserved slots in which items have yet to be fully instantiated
if (item) then
return item, x, y
end
end
end
-- This function may pretty heavy.
--- Returns a table of all the items that an `Inventory` has.
-- @realm shared
-- @bool onlyMain Whether or not to exclude bags from this search.
-- @treturn table The items this `Inventory` has.
function META:GetBags()
local invs = {}
for _, v in pairs(self.slots) do
for _, v2 in pairs(v) do
if (istable(v2) and v2.data) then
local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id)
if (!table.HasValue(invs, isBag)) then
if (isBag and isBag != self:GetID()) then
invs[#invs + 1] = isBag
end
end
end
end
end
return invs
end
--- Returns the item with the given unique ID (e.g `"handheld_radio"`) if it exists in this inventory.
-- This method checks both
-- this inventory, and any bags that this inventory has inside of it.
-- @realm shared
-- @string targetID Unique ID of the item to look for
-- @tab[opt] data Item data to check for
-- @treturn[1] Item Item that belongs to this inventory with the given criteria
-- @treturn[2] bool `false` if the item does not exist
-- @see HasItems
-- @see HasItemOfBase
-- @usage local item = inventory:HasItem("handheld_radio")
--
-- if (item) then
-- -- do something with the item table
-- end
function META:HasItem(targetID, data)
for k, _ in self:Iter() do
if (k.uniqueID == targetID) then
if (data) then
local itemData = k.data
local bFound = true
for dataKey, dataVal in pairs(data) do
if (itemData[dataKey] != dataVal) then
bFound = false
break
end
end
if (!bFound) then
continue
end
end
return k
end
end
return false
end
--- Checks whether or not the `Inventory` has a table of items.
-- This function takes a table with **no** keys and runs in order of first item > last item,
--this is due to the usage of the `#` operator in the function.
--
-- @realm shared
-- @tab targetIDs A table of `Item` Unique ID's.
-- @treturn[1] bool true Whether or not the `Inventory` has all of the items.
-- @treturn[1] table targetIDs Your provided targetIDs table, but it will be empty.
-- @treturn[2] bool false
-- @treturn[2] table targetIDs Table consisting of the items the `Inventory` did **not** have.
-- @usage local itemFilter = {'water', 'water_sparkling'}
-- if not Entity(1):GetCharacter():GetInventory():HasItems(itemFilter) then return end
-- -- Filters out if this player has both a water, and a sparkling water.
function META:HasItems(targetIDs)
local count = #targetIDs -- assuming array
targetIDs = table.Copy(targetIDs)
for item, _ in self:Iter() do
for k, targetID in ipairs(targetIDs) do
if (item.uniqueID == targetID) then
table.remove(targetIDs, k)
count = count - 1
break
end
end
end
return count <= 0, targetIDs
end
--- Whether or not an `Inventory` has an item of a base, optionally with specified data.
-- This function has an optional `data` argument, which will take a `table`.
-- it will match if the data of the item is correct or not.
--
-- Items which are a base will automatically have base_ prefixed to their Unique ID, if you are having
-- trouble finding your base, that is probably why.
-- @realm shared
-- @string baseID The Item Base's Unique ID.
-- @tab[opt] data The Item's data to compare against.
-- @treturn[1] item The first `Item` of `baseID` that is found and there is no `data` argument or `data` was matched.
-- @treturn[2] false If no `Item`s of `baseID` is found or the `data` argument, if specified didn't match.
-- @usage local bHasWeaponEquipped = Entity(1):GetCharacter():GetInventory():HasItemOfBase('base_weapons', {['equip'] = true})
-- if bHasWeaponEquipped then
-- Entity(1):Notify('One gun is fun, two guns is Woo-tastic.')
-- end
-- -- Notifies the player that they should get some more guns.
function META:HasItemOfBase(baseID, data)
for k, _ in self:Iter() do
if (k.base == baseID) then
if (data) then
local itemData = k.data
local bFound = true
for dataKey, dataVal in pairs(data) do
if (itemData[dataKey] != dataVal) then
bFound = false
break
end
end
if (!bFound) then
continue
end
end
return k
end
end
return false
end
if (SERVER) then
--- Sends a specific slot to a character.
-- This will *not* send all of the slots of the `Item` to the character, items can occupy multiple slots.
--
-- This will call `OnSendData` on the Item using all of the `Inventory`'s receivers.
--
-- This function should *not* be used to sync an entire inventory, if you need to do that, use `AddReceiver` and `Sync`.
-- @realm server
-- @internal
-- @number x The Inventory x position to send.
-- @number y The Inventory y position to send.
-- @item[opt] item The item to send, if any.
-- @see AddReceiver
-- @see Sync
function META:SendSlot(x, y, item)
local receivers = self:GetReceivers()
local sendData = item and item.data and !table.IsEmpty(item.data) and item.data or {}
net.Start("ixInventorySet")
net.WriteUInt(self:GetID(), 32)
net.WriteUInt(x, 6)
net.WriteUInt(y, 6)
net.WriteString(item and item.uniqueID or "")
net.WriteUInt(item and item.id or 0, 32)
net.WriteUInt(self.owner or 0, 32)
net.WriteTable(sendData)
net.Send(receivers)
if (item) then
for _, v in pairs(receivers) do
item:Call("OnSendData", v)
end
end
end
--- Sets whether or not an `Inventory` should save.
-- This will prevent an `Inventory` from updating in the Database, if the inventory is already saved,
-- it will not be deleted when unloaded.
-- @realm server
-- @bool bNoSave Whether or not the Inventory should save.
function META:SetShouldSave(bNoSave)
self.noSave = bNoSave
end
--- Gets whether or not an `Inventory` should save.
-- Inventories that are marked to not save will not update in the Database, if they inventory is already saved,
-- it will not be deleted when unloaded.
-- @realm server
-- @treturn[1] bool Returns the field `noSave`.
-- @treturn[2] bool Returns true if the field `noSave` is not registered to this inventory.
function META:GetShouldSave()
return self.noSave or true
end
--- Add an item to the inventory.
-- @realm server
-- @param uniqueID The item unique ID (e.g `"handheld_radio"`) or instance ID (e.g `1024`) to add to the inventory
-- @number[opt=1] quantity The quantity of the item to add
-- @tab data Item data to add to the item
-- @number[opt=nil] x The X position for the item
-- @number[opt=nil] y The Y position for the item
-- @bool[opt=false] noReplication Whether or not the item's addition should not be replicated
-- @treturn[1] bool Whether the add was successful or not
-- @treturn[1] string The error, if applicable
-- @treturn[2] number The X position that the item was added to
-- @treturn[2] number The Y position that the item was added to
-- @treturn[2] number The inventory ID that the item was added to
function META:Add(uniqueID, quantity, data, x, y, noReplication)
quantity = quantity or 1
if (quantity < 1) then
return false, "noOwner"
end
if (!isnumber(uniqueID) and quantity > 1) then
for _ = 1, quantity do
local bSuccess, error = self:Add(uniqueID, 1, data)
if (!bSuccess) then
return false, error
end
end
return true
end
local client = self.GetOwner and self:GetOwner() or nil
local item = isnumber(uniqueID) and ix.item.instances[uniqueID] or ix.item.list[uniqueID]
local targetInv = self
local bagInv
if (!item) then
return false, "invalidItem"
end
if (isnumber(uniqueID)) then
local oldInvID = item.invID
if (!x and !y) then
x, y, bagInv = self:FindEmptySlot(item.width, item.height)
end
if (bagInv) then
targetInv = bagInv
end
-- we need to check for owner since the item instance already exists
if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter() and
item:GetPlayerID() == client:SteamID64() and item:GetCharacterID() != client:GetCharacter():GetID()) then
return false, "itemOwned"
end
if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv) == false) then
return false, "notAllowed"
end
if (x and y) then
targetInv.slots[x] = targetInv.slots[x] or {}
targetInv.slots[x][y] = true
item.gridX = x
item.gridY = y
item.invID = targetInv:GetID()
for x2 = 0, item.width - 1 do
local index = x + x2
for y2 = 0, item.height - 1 do
targetInv.slots[index] = targetInv.slots[index] or {}
targetInv.slots[index][y + y2] = item
end
end
if (!noReplication) then
targetInv:SendSlot(x, y, item)
end
if (!self.noSave) then
local query = mysql:Update("ix_items")
query:Update("inventory_id", targetInv:GetID())
query:Update("x", x)
query:Update("y", y)
query:Where("item_id", item.id)
query:Execute()
end
hook.Run("InventoryItemAdded", ix.item.inventories[oldInvID], targetInv, item)
return x, y, targetInv:GetID()
else
return false, "noFit"
end
else
if (!x and !y) then
x, y, bagInv = self:FindEmptySlot(item.width, item.height)
end
if (bagInv) then
targetInv = bagInv
end
if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv) == false) then
return false, "notAllowed"
end
if (x and y) then
for x2 = 0, item.width - 1 do
local index = x + x2
for y2 = 0, item.height - 1 do
targetInv.slots[index] = targetInv.slots[index] or {}
targetInv.slots[index][y + y2] = true
end
end
local characterID
local playerID
if (self.owner) then
local character = ix.char.loaded[self.owner]
if (character) then
characterID = character.id
playerID = character.steamID
end
end
ix.item.Instance(targetInv:GetID(), uniqueID, data, x, y, function(newItem)
newItem.gridX = x
newItem.gridY = y
for x2 = 0, newItem.width - 1 do
local index = x + x2
for y2 = 0, newItem.height - 1 do
targetInv.slots[index] = targetInv.slots[index] or {}
targetInv.slots[index][y + y2] = newItem
end
end
if (!noReplication) then
targetInv:SendSlot(x, y, newItem)
end
hook.Run("InventoryItemAdded", nil, targetInv, newItem)
end, characterID, playerID)
return x, y, targetInv:GetID()
else
return false, "noFit"
end
end
end
--- Syncs the `Inventory` to the receiver.
-- This will call Item.OnSendData on every item in the `Inventory`.
-- @realm server
-- @player receiver The player to
function META:Sync(receiver)
local slots = {}
for x, items in pairs(self.slots) do
for y, item in pairs(items) do
if (istable(item) and item.gridX == x and item.gridY == y) then
slots[#slots + 1] = {x, y, item.uniqueID, item.id, item.data}
end
end
end
net.Start("ixInventorySync")
net.WriteTable(slots)
net.WriteUInt(self:GetID(), 32)
net.WriteUInt(self.w, 6)
net.WriteUInt(self.h, 6)
net.WriteType(self.owner)
net.WriteTable(self.vars or {})
net.Send(receiver)
for k, _ in self:Iter() do
k:Call("OnSendData", receiver)
end
end
end
ix.meta.inventory = META
================================================
FILE: gamemode/core/meta/sh_item.lua
================================================
--[[--
Interactable entities that can be held in inventories.
Items are objects that are contained inside of an `Inventory`, or as standalone entities if they are dropped in the world. They
usually have functionality that provides more gameplay aspects to the schema. For example, the zipties in the HL2 RP schema
allow a player to tie up and search a player.
For an item to have an actual presence, they need to be instanced (usually with `ix.item.Instance`). Items describe the
properties, while instances are a clone of these properties that can have their own unique data (e.g an ID card will have the
same name but different numerical IDs). You can think of items as the class, while instances are objects of the `Item` class.
## Creating item classes (`ItemStructure`)
Item classes are defined in their own file inside of your schema or plugin's `items/` folder. In these item class files you
specify how instances of the item behave. This includes default values for basic things like the item's name and description,
to more advanced things by overriding extra methods from an item base. See `ItemStructure` for information on how to define
a basic item class.
Item classes in this folder are automatically loaded by Helix when the server starts up.
## Item bases
If many items share the same functionality (i.e a can of soda and a bottle of water can both be consumed), then you might want
to consider using an item base to reduce the amount of duplication for these items. Item bases are defined the same way as
regular item classes, but they are placed in the `items/base/` folder in your schema or plugin. For example, a `consumables`
base would be in `items/base/sh_consumables.lua`.
Any items that you want to use this base must be placed in a subfolder that has the name of the base you want that item to use.
For example, for a bottled water item to use the consumable base, it must be placed in `items/consumables/sh_bottled_water.lua`.
This also means that you cannot place items into subfolders as you wish, since the framework will try to use an item base that
doesn't exist.
The default item bases that come with Helix are:
- `ammo` - provides ammo to any items with the `weapons` base
- `bags` - holds an inventory that other items can be stored inside of
- `outfit` - changes the appearance of the player that wears it
- `pacoutfit` - changes the appearance of the player that wears it using PAC3
- `weapons` - makes any SWEP into an item that can be equipped
These item bases usually come with extra values and methods that you can define/override in order to change their functionality.
You should take a look at the source code for these bases to see their capabilities.
## Item functions (`ItemFunctionStructure`)
Requiring players to interact with items in order for them to do something is quite common. As such, there is already a built-in
mechanism to allow players to right-click items and show a list of available options. Item functions are defined in your item
class file in the `ITEM.functions` table. See `ItemFunctionStructure` on how to define them.
Helix comes with `drop`, `take`, and `combine` item functions by default that allows items to be dropped from a player's
inventory, picked up from the world, and combining items together. These can be overridden by defining an item function
in your item class file with the same name. See the `bags` base for example usage of the `combine` item function.
## Item icons (`ItemIconStructure`)
Icons for items sometimes don't line up quite right, in which case you can modify an item's `iconCam` value and line up the
rendered model as needed. See `ItemIconStructure` for more details.
]]
-- @classmod Item
--[[--
All item functions live inside of an item's `functions` table. An item function entry includes a few methods and fields you can
use to customize the functionality and appearance of the item function. An example item function is below:
-- this item function's unique ID is "MyFunction"
ITEM.functions.MyFunction = {
name = "myFunctionPhrase", -- uses the "myFunctionPhrase" language phrase when displaying in the UI
tip = "myFunctionDescription", -- uses the "myFunctionDescription" language phrase when displaying in the UI
icon = "icon16/add.png", -- path to the icon material
OnRun = function(item)
local client = item.player
local entity = item.entity -- only set if this is function is being ran while the item is in the world
if (IsValid(client)) then
client:ChatPrint("This is a test.")
if (IsValid(entity)) then
client:ChatPrint(entity:GetName())
end
end
-- do not remove this item from the owning player's inventory
return false
end,
OnCanRun = function(item)
-- only allow admins to run this item function
local client = item.player
return IsValid(client) and client:IsAdmin()
end
}
]]
-- @table ItemFunctionStructure
-- @realm shared
-- @field[type=string,opt] name Language phrase to use when displaying this item function's name in the UI. If not specified,
-- then it will use the unique ID of the item function
-- @field[type=string,opt] tip Language phrase to use when displaying this item function's detailed description in the UI
-- @field[type=string,opt] icon Path to the material to use when displaying this item function's icon
-- @field[type=function] OnRun Function to call when the item function is ran. This function is **ONLY** ran on the server.
--
-- The only argument passed into this function is the instance of the item being called. The instance will have its `player`
-- field set if the item function is being ran by a player (which it should be most of the time). It will also have its `entity`
-- field set if the item function is being ran while the item is in the world, and not in a player's inventory.
--
-- The item will be removed after the item function is ran. If you want to prevent this behaviour, then you can return `false`
-- in this function. See the example above.
-- @field[type=function] OnCanRun Function to call when checking whether or not this item function can be ran. This function is
-- ran **BOTH** on the client and server.
--
-- The arguments are the same as `OnCanRun`, and the `player` and `entity` fields will be set on the item instance accordingly.
-- Returning `true` will allow the item function to be ran. Returning `false` will prevent it from running and additionally
-- hide it from the UI. See the example above.
-- @field[type=function,opt] OnClick This function is called when the player clicks on this item function's entry in the UI.
-- This function is ran **ONLY** on the client, and is only ran if `OnCanRun` succeeds.
--
-- The same arguments from `OnCanRun` and `OnRun` apply to this function.
--[[--
Changing the way an item's icon is rendered is done by modifying the location and angle of the model, as well as the FOV of the
camera. You can tweak the values in code, or use the `ix_dev_icon` console command to visually position the model and camera. An
example entry for an item's icon is below:
ITEM.iconCam = {
pos = Vector(0, 0, 60),
ang = Angle(90, 0, 0),
fov = 45
}
Note that this will probably not work for your item's specific model, since every model has a different size, origin, etc. All
item icons need to be tweaked individually.
]]
-- @table ItemIconStructure
-- @realm client
-- @field[type=vector] pos Location of the model relative to the camera. +X is forward, +Z is up
-- @field[type=angle] ang Angle of the model
-- @field[type=number] fov FOV of the camera
--[[--
When creating an item class, the file will have a global table `ITEM` set that you use to define the item's values/methods. An
example item class is below:
`items/sh_brick.lua`
ITEM.name = "Brick"
ITEM.description = "A brick. Pretty self-explanatory. You can eat it but you'll probably lose some teeth."
ITEM.model = Model("models/props_debris/concrete_cynderblock001.mdl")
ITEM.width = 1
ITEM.height = 1
ITEM.price = 25
Note that the below list only includes the default fields available for *all* items, and not special ones defined in custom
item bases.
]]
-- @table ItemStructure
-- @realm shared
-- @field[type=string] name Display name of the item
-- @field[type=string] description Detailed description of the item
-- @field[type=string] model Model to use for the item's icon and when it's dropped in the world
-- @field[type=number,opt=1] width Width of the item in grid cells
-- @field[type=number,opt=1] height Height of the item in grid cells
-- @field[type=number,opt=0] price How much money it costs to purchase this item in the business menu
-- @field[type=string,opt] category Name of the category this item belongs to - mainly used for the business menu
-- @field[type=boolean,opt=false] noBusiness Whether or not to disallow purchasing this item in the business menu
-- @field[type=table,opt] factions List of factions allowed to purchase this item in the business menu
-- @field[type=table,opt] classes List of character classes allowed to purchase this item in the business menu. Classes are
-- checked after factions, so the character must also be in an allowed faction
-- @field[type=string,opt] flag List of flags (as a string - e.g `"a"` or `"abc"`) allowed to purchase this item in the
-- business menu. Flags are checked last, so the character must also be in an allowed faction and class
-- @field[type=ItemIconStructure,opt] iconCam How to render this item's icon
-- @field[type=table,opt] functions List of all item functions that this item has. See `ItemFunctionStructure` on how to define
-- new item functions
local ITEM = ix.meta.item or {}
ITEM.__index = ITEM
ITEM.name = "Undefined"
ITEM.description = ITEM.description or "An item that is undefined."
ITEM.id = ITEM.id or 0
ITEM.uniqueID = "undefined"
--- Returns a string representation of this item.
-- @realm shared
-- @treturn string String representation
-- @usage print(ix.item.instances[1])
-- > "item[1]"
function ITEM:__tostring()
return "item["..self.uniqueID.."]["..self.id.."]"
end
--- Returns true if this item is equal to another item. Internally, this checks item IDs.
-- @realm shared
-- @item other Item to compare to
-- @treturn bool Whether or not this item is equal to the given item
-- @usage print(ix.item.instances[1] == ix.item.instances[2])
-- > false
function ITEM:__eq(other)
return self:GetID() == other:GetID()
end
--- Returns this item's database ID. This is guaranteed to be unique.
-- @realm shared
-- @treturn number Unique ID of item
function ITEM:GetID()
return self.id
end
--- Returns the name of the item.
-- @realm shared
-- @treturn string The name of the item
function ITEM:GetName()
return (CLIENT and L(self.name) or self.name)
end
--- Returns the description of the item.
-- @realm shared
-- @treturn string The description of the item
function ITEM:GetDescription()
if (!self.description) then return "ERROR" end
return L(self.description or "noDesc")
end
--- Returns the model of the item.
-- @realm shared
-- @treturn string The model of the item
function ITEM:GetModel()
return self.model
end
--- Returns the skin of the item.
-- @realm shared
-- @treturn number The skin of the item
function ITEM:GetSkin()
return self.skin or 0
end
function ITEM:GetMaterial()
return nil
end
--- Returns the ID of the owning character, if one exists.
-- @realm shared
-- @treturn number The owning character's ID
function ITEM:GetCharacterID()
return self.characterID
end
--- Returns the SteamID64 of the owning player, if one exists.
-- @realm shared
-- @treturn number The owning player's SteamID64
function ITEM:GetPlayerID()
return self.playerID
end
--- A utility function which prints the item's details.
-- @realm shared
-- @bool[opt=false] detail Whether additional detail should be printed or not(Owner, X position, Y position)
function ITEM:Print(detail)
if (detail == true) then
print(Format("%s[%s]: >> [%s](%s,%s)", self.uniqueID, self.id, self.owner, self.gridX, self.gridY))
else
print(Format("%s[%s]", self.uniqueID, self.id))
end
end
--- A utility function printing the item's stored data.
-- @realm shared
function ITEM:PrintData()
self:Print(true)
print("ITEM DATA:")
for k, v in pairs(self.data) do
print(Format("[%s] = %s", k, v))
end
end
--- Calls one of the item's methods.
-- @realm shared
-- @string method The method to be called
-- @player client The client to pass when calling the method, if applicable
-- @entity entity The eneity to pass when calling the method, if applicable
-- @param ... Arguments to pass to the method
-- @return The values returned by the method
function ITEM:Call(method, client, entity, ...)
local oldPlayer, oldEntity = self.player, self.entity
self.player = client or self.player
self.entity = entity or self.entity
if (isfunction(self[method])) then
local results = {self[method](self, ...)}
self.player = nil
self.entity = nil
return unpack(results)
end
self.player = oldPlayer
self.entity = oldEntity
end
--- Returns the player that owns this item.
-- @realm shared
-- @treturn player Player owning this item
function ITEM:GetOwner()
local inventory = ix.item.inventories[self.invID]
if (inventory) then
return inventory.GetOwner and inventory:GetOwner()
end
local id = self:GetID()
for _, v in player.Iterator() do
local character = v:GetCharacter()
if (character and character:GetInventory() and character:GetInventory():GetItemByID(id)) then
return v
end
end
end
--- Sets a key within the item's data.
-- @realm shared
-- @string key The key to store the value within
-- @param[opt=nil] value The value to store within the key
-- @tab[opt=nil] receivers The players to replicate the data on
-- @bool[opt=false] noSave Whether to disable saving the data on the database or not
-- @bool[opt=false] noCheckEntity Whether to disable setting the data on the entity, if applicable
function ITEM:SetData(key, value, receivers, noSave, noCheckEntity)
self.data = self.data or {}
self.data[key] = value
if (SERVER) then
if (!noCheckEntity) then
local ent = self:GetEntity()
if (IsValid(ent)) then
local data = ent:GetNetVar("data", {})
data[key] = value
ent:SetNetVar("data", data)
end
end
end
local inventory = ix.item.inventories[self.invID]
if (receivers != false) then
local targets = receivers or (inventory and inventory.GetReceivers and inventory:GetReceivers()) or self:GetOwner()
if (targets) then
net.Start("ixInventoryData")
net.WriteUInt(self:GetID(), 32)
net.WriteString(key)
net.WriteType(value)
net.Send(targets)
end
end
if (!noSave and ix.db) then
local query = mysql:Update("ix_items")
query:Update("data", util.TableToJSON(self.data))
query:Where("item_id", self:GetID())
query:Execute()
end
end
--- Returns the value stored on a key within the item's data.
-- @realm shared
-- @string key The key in which the value is stored
-- @param[opt=nil] default The value to return in case there is no value stored in the key
-- @return The value stored within the key
function ITEM:GetData(key, default)
self.data = self.data or {}
if (self.data) then
if (key == true) then
return self.data
end
local value = self.data[key]
if (value != nil) then
return value
elseif (IsValid(self.entity)) then
local data = self.entity:GetNetVar("data", {})
value = data[key]
if (value != nil) then
return value
end
end
else
self.data = {}
end
if (default != nil) then
return default
end
return
end
--- Changes the function called on specific events for the item.
-- @realm shared
-- @string name The name of the hook
-- @func func The function to call once the event occurs
function ITEM:Hook(name, func)
if (name) then
self.hooks[name] = func
end
end
--- Changes the function called after hooks for specific events for the item.
-- @realm shared
-- @string name The name of the hook
-- @func func The function to call after the original hook was called
function ITEM:PostHook(name, func)
if (name) then
self.postHooks[name] = func
end
end
--- Removes the item.
-- @realm shared
-- @bool bNoReplication Whether or not the item's removal should not be replicated.
-- @bool bNoDelete Whether or not the item should not be fully deleted
-- @treturn bool Whether the item was successfully deleted or not
function ITEM:Remove(bNoReplication, bNoDelete)
local inv = ix.item.inventories[self.invID]
local bFailed = false
if (self.invID > 0 and inv) then
for x = self.gridX, self.gridX + (self.width - 1) do
if (inv.slots[x]) then
for y = self.gridY, self.gridY + (self.height - 1) do
local item = inv.slots[x][y]
if (item and item.id == self.id) then
inv.slots[x][y] = nil
else
bFailed = true
end
end
else
bFailed = true
end
end
if (bFailed) then
local items = {}
for _, v in pairs(ix.item.instances) do
if (v.invID == self.invID and v.id != self.id) then
items[#items + 1] = v
end
end
inv.slots = {}
for _, v in ipairs(items) do
for x = v.gridX, v.gridX + (v.width - 1) do
for y = v.gridY, v.gridY + (v.height - 1) do
inv.slots[x] = inv.slots[x] or {}
inv.slots[x][y] = v
end
end
end
end
else
-- @todo definition probably isn't needed
inv = ix.item.inventories[self.invID]
if (inv) then
ix.item.inventories[self.invID][self.id] = nil
end
end
if (SERVER and !bNoReplication) then
local entity = self:GetEntity()
if (IsValid(entity)) then
entity:Remove()
end
local receivers = inv.GetReceivers and inv:GetReceivers()
if (self.invID != 0 and istable(receivers)) then
if (bFailed) then
inv:Sync(receivers)
else
net.Start("ixInventoryRemove")
net.WriteUInt(self.id, 32)
net.WriteUInt(self.invID, 32)
net.Send(receivers)
end
end
if (!bNoDelete) then
local item = ix.item.instances[self.id]
if (inv and inv.owner) then
hook.Run("InventoryItemRemoved", inv, item)
end
if (item and item.OnRemoved) then
item:OnRemoved()
end
local query = mysql:Delete("ix_items")
query:Where("item_id", self.id)
query:Execute()
ix.item.instances[self.id] = nil
end
end
return true
end
if (SERVER) then
--- Returns the item's entity.
-- @realm server
-- @treturn entity The entity of the item
function ITEM:GetEntity()
local id = self:GetID()
for _, v in ipairs(ents.FindByClass("ix_item")) do
if (v.ixItemID == id) then
return v
end
end
end
--- Spawn an item entity based off the item table.
-- @realm server
-- @param[type=vector] position The position in which the item's entity will be spawned
-- @param[type=angle] angles The angles at which the item's entity will spawn
-- @treturn entity The spawned entity
function ITEM:Spawn(position, angles)
-- Check if the item has been created before.
if (ix.item.instances[self.id]) then
local client
-- Spawn the actual item entity.
local entity = ents.Create("ix_item")
entity:Spawn()
entity:SetAngles(angles or Angle(0, 0, 0))
entity:SetItem(self.id)
-- If the first argument is a player, then we will find a position to drop
-- the item based off their aim.
if (type(position) == "Player") then
client = position
position = position:GetItemDropPos(entity)
end
entity:SetPos(position)
if (IsValid(client)) then
entity.ixSteamID = client:SteamID()
entity.ixCharID = client:GetCharacter():GetID()
entity:SetNetVar("owner", entity.ixCharID)
end
hook.Run("OnItemSpawned", entity)
return entity
end
end
--- Transfers an item to a specific inventory.
-- @realm server
-- @number invID The inventory to transfer the item to
-- @number x The X position to which the item should be transferred on the new inventory
-- @number y The Y position to which the item should be transferred on the new inventory
-- @player client The player to which the item is being transferred
-- @bool noReplication Whether there should be no replication of the transferral
-- @bool isLogical Whether or not an entity should spawn if the item is transferred to the world
-- @treturn[1] bool Whether the transfer was successful or not
-- @treturn[1] string The error, if applicable
function ITEM:Transfer(invID, x, y, client, noReplication, isLogical)
invID = invID or 0
if (self.invID == invID) then
return false, "same inv"
end
local inventory = ix.item.inventories[invID]
local curInv = ix.item.inventories[self.invID or 0]
if (curInv and !IsValid(client)) then
client = curInv.GetOwner and curInv:GetOwner() or nil
end
-- check if this item doesn't belong to another one of this player's characters
local itemPlayerID = self:GetPlayerID()
local itemCharacterID = self:GetCharacterID()
if (!self.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then
local playerID = client:SteamID64()
local characterID = client:GetCharacter():GetID()
if (itemPlayerID and itemCharacterID) then
if (itemPlayerID == playerID and itemCharacterID != characterID) then
return false, "itemOwned"
end
else
self.characterID = characterID
self.playerID = playerID
local query = mysql:Update("ix_items")
query:Update("character_id", characterID)
query:Update("player_id", playerID)
query:Where("item_id", self:GetID())
query:Execute()
end
end
if (hook.Run("CanTransferItem", self, curInv, inventory) == false) then
return false, "notAllowed"
end
local authorized = false
if (inventory and inventory.OnAuthorizeTransfer and inventory:OnAuthorizeTransfer(client, curInv, self)) then
authorized = true
end
if (!authorized and self.CanTransfer and self:CanTransfer(curInv, inventory) == false) then
return false, "notAllowed"
end
if (curInv) then
if (invID and invID > 0 and inventory) then
local targetInv = inventory
local bagInv
if (!x and !y) then
x, y, bagInv = inventory:FindEmptySlot(self.width, self.height)
end
if (bagInv) then
targetInv = bagInv
end
if (!x or !y) then
return false, "noFit"
end
local prevID = self.invID
local status, result = targetInv:Add(self.id, nil, nil, x, y, noReplication)
if (status) then
if (self.invID > 0 and prevID != 0) then
-- we are transferring this item from one inventory to another
curInv:Remove(self.id, false, true, true)
if (self.OnTransferred) then
self:OnTransferred(curInv, inventory)
end
hook.Run("OnItemTransferred", self, curInv, inventory)
return true
elseif (self.invID > 0 and prevID == 0) then
-- we are transferring this item from the world to an inventory
ix.item.inventories[0][self.id] = nil
if (self.OnTransferred) then
self:OnTransferred(curInv, inventory)
end
hook.Run("OnItemTransferred", self, curInv, inventory)
return true
end
else
return false, result
end
elseif (IsValid(client)) then
-- we are transferring this item from an inventory to the world
self.invID = 0
curInv:Remove(self.id, false, true)
local query = mysql:Update("ix_items")
query:Update("inventory_id", 0)
query:Where("item_id", self.id)
query:Execute()
inventory = ix.item.inventories[0]
inventory[self:GetID()] = self
if (self.OnTransferred) then
self:OnTransferred(curInv, inventory)
end
hook.Run("OnItemTransferred", self, curInv, inventory)
if (!isLogical) then
return self:Spawn(client)
end
return true
else
return false, "noOwner"
end
else
return false, "invalidInventory"
end
end
end
ix.meta.item = ITEM
================================================
FILE: gamemode/core/meta/sh_player.lua
================================================
--[[--
Physical representation of connected player.
`Player`s are a type of `Entity`. They are a physical representation of a `Character` - and can possess at most one `Character`
object at a time that you can interface with.
See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Player) for all other methods that the `Player` class has.
]]
-- @classmod Player
local meta = FindMetaTable("Player")
if (SERVER) then
--- Returns the amount of time the player has played on the server.
-- @realm shared
-- @treturn number Number of seconds the player has played on the server
function meta:GetPlayTime()
return self.ixPlayTime + (RealTime() - (self.ixJoinTime or RealTime()))
end
else
ix.playTime = ix.playTime or 0
function meta:GetPlayTime()
return ix.playTime + (RealTime() - ix.joinTime or 0)
end
end
--- Returns `true` if the player has their weapon raised.
-- @realm shared
-- @treturn bool Whether or not the player has their weapon raised
function meta:IsWepRaised()
return self:GetNetVar("raised", false)
end
--- Returns `true` if the player is restricted - that is to say that they are considered "bound" and cannot interact with
-- objects normally (e.g hold weapons, use items, etc). An example of this would be a player in handcuffs.
-- @realm shared
-- @treturn bool Whether or not the player is restricted
function meta:IsRestricted()
return self:GetNetVar("restricted", false)
end
--- Returns `true` if the player is able to shoot their weapon.
-- @realm shared
-- @treturn bool Whether or not the player can shoot their weapon
function meta:CanShootWeapon()
return self:GetNetVar("canShoot", true)
end
local vectorLength2D = FindMetaTable("Vector").Length2D
--- Returns `true` if the player is running. Running in this case means that their current speed is greater than their
-- regularly set walk speed.
-- @realm shared
-- @treturn bool Whether or not the player is running
function meta:IsRunning()
return vectorLength2D(self:GetVelocity()) > (self:GetWalkSpeed() + 10)
end
--- Returns `true` if the player currently has a female model. This checks if the model has `female`, `alyx` or `mossman` in its
-- name, or if the player's model class is `citizen_female`.
-- @realm shared
-- @treturn bool Whether or not the player has a female model
function meta:IsFemale()
local model = self:GetModel():lower()
return (model:find("female") or model:find("alyx") or model:find("mossman")) != nil or
ix.anim.GetModelClass(model) == "citizen_female"
end
--- Whether or not this player is stuck and cannot move.
-- @realm shared
-- @treturn bool Whether or not this player is stuck
function meta:IsStuck()
return util.TraceEntity({
start = self:GetPos(),
endpos = self:GetPos(),
filter = self
}, self).StartSolid
end
--- Returns a good position in front of the player for an entity to be placed. This is usually used for item entities.
-- @realm shared
-- @entity entity Entity to get a position for
-- @treturn vector Best guess for a good drop position in front of the player
-- @usage local position = client:GetItemDropPos(entity)
-- entity:SetPos(position)
function meta:GetItemDropPos(entity)
local data = {}
local trace
data.start = self:GetShootPos()
data.endpos = self:GetShootPos() + self:GetAimVector() * 86
data.filter = self
if (IsValid(entity)) then
-- use a hull trace if there's a valid entity to avoid collisions
local mins, maxs = entity:GetRotatedAABB(entity:OBBMins(), entity:OBBMaxs())
data.mins = mins
data.maxs = maxs
data.filter = {entity, self}
trace = util.TraceHull(data)
else
-- trace along the normal for a few units so we can attempt to avoid a collision
trace = util.TraceLine(data)
data.start = trace.HitPos
data.endpos = data.start + trace.HitNormal * 48
trace = util.TraceLine(data)
end
return trace.HitPos
end
--- Performs a time-delay action that requires this player to look at an entity. If this player looks away from the entity
-- before the action timer completes, the action is cancelled. This is usually used in conjunction with `SetAction` to display
-- progress to the player.
-- @realm shared
-- @entity entity that this player must look at
-- @func callback Function to call when the timer completes
-- @number time How much time in seconds this player must look at the entity for
-- @func[opt=nil] onCancel Function to call when the timer has been cancelled
-- @number[opt=96] distance Maximum distance a player can move away from the entity before the action is cancelled
-- @see SetAction
-- @usage client:SetAction("Searching...", 4) -- for displaying the progress bar
-- client:DoStaredAction(entity, function()
-- print("hello!")
-- end)
-- -- prints "hello!" after looking at the entity for 4 seconds
function meta:DoStaredAction(entity, callback, time, onCancel, distance)
local uniqueID = "ixStare"..self:SteamID64()
local data = {}
data.filter = self
timer.Create(uniqueID, 0.1, time / 0.1, function()
if (IsValid(self) and IsValid(entity)) then
data.start = self:GetShootPos()
data.endpos = data.start + self:GetAimVector()*(distance or 96)
if (util.TraceLine(data).Entity != entity) then
timer.Remove(uniqueID)
if (onCancel) then
onCancel()
end
elseif (callback and timer.RepsLeft(uniqueID) == 0) then
callback()
end
else
timer.Remove(uniqueID)
if (onCancel) then
onCancel()
end
end
end)
end
--- Resets all bodygroups this player's model has to their defaults (`0`).
-- @realm shared
function meta:ResetBodygroups()
for i = 0, (self:GetNumBodyGroups() - 1) do
self:SetBodygroup(i, 0)
end
end
if (SERVER) then
util.AddNetworkString("ixActionBar")
util.AddNetworkString("ixActionBarReset")
util.AddNetworkString("ixStringRequest")
--- Sets whether or not this player's current weapon is raised.
-- @realm server
-- @bool bState Whether or not the raise the weapon
-- @entity[opt=GetActiveWeapon()] weapon Weapon to raise or lower. You should pass this argument if you already have a
-- reference to this player's current weapon to avoid an expensive lookup for this player's current weapon.
function meta:SetWepRaised(bState, weapon)
weapon = weapon or self:GetActiveWeapon()
if (IsValid(weapon)) then
local bCanShoot = !bState and weapon.FireWhenLowered or bState
self:SetNetVar("raised", bState)
if (bCanShoot) then
-- delay shooting while the raise animation is playing
timer.Create("ixWeaponRaise" .. self:SteamID64(), 1, 1, function()
if (IsValid(self)) then
self:SetNetVar("canShoot", true)
end
end)
else
timer.Remove("ixWeaponRaise" .. self:SteamID64())
self:SetNetVar("canShoot", false)
end
else
timer.Remove("ixWeaponRaise" .. self:SteamID64())
self:SetNetVar("raised", false)
self:SetNetVar("canShoot", false)
end
end
--- Inverts this player's weapon raised state. You should use `SetWepRaised` instead of this if you already have a reference
-- to this player's current weapon.
-- @realm server
function meta:ToggleWepRaised()
local weapon = self:GetActiveWeapon()
if (!IsValid(weapon) or
weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()] or
weapon.IsAlwaysLowered or weapon.NeverRaised) then
return
end
self:SetWepRaised(!self:IsWepRaised(), weapon)
if (self:IsWepRaised() and weapon.OnRaised) then
weapon:OnRaised()
elseif (!self:IsWepRaised() and weapon.OnLowered) then
weapon:OnLowered()
end
end
--- Performs a delayed action that requires this player to hold use on an entity. This is displayed to this player as a
-- closing ring over their crosshair.
-- @realm server
-- @number time How much time in seconds this player has to hold use for
-- @entity entity Entity that this player must be looking at
-- @func callback Function to run when the timer completes. It will be ran right away if `time` is `0`. Returning `false` in
-- the callback will not mark this interaction as dirty if you're managing the interaction state manually.
function meta:PerformInteraction(time, entity, callback)
if (!IsValid(entity) or entity.ixInteractionDirty) then
return
end
if (time > 0) then
self.ixInteractionTarget = entity
self.ixInteractionCharacter = self:GetCharacter():GetID()
timer.Create("ixCharacterInteraction" .. self:SteamID(), time, 1, function()
if (IsValid(self) and IsValid(entity) and IsValid(self.ixInteractionTarget) and
self.ixInteractionCharacter == self:GetCharacter():GetID()) then
local useEntity = self:GetUseEntity()
if (IsValid(useEntity) and useEntity == self.ixInteractionTarget and !useEntity.ixInteractionDirty) then
if (callback(self) != false) then
useEntity.ixInteractionDirty = true
end
end
end
end)
else
if (callback(self) != false) then
entity.ixInteractionDirty = true
end
end
end
--- Displays a progress bar for this player that takes the given amount of time to complete.
-- @realm server
-- @string text Text to display above the progress bar
-- @number[opt=5] time How much time in seconds to wait before the timer completes
-- @func callback Function to run once the timer completes
-- @number[opt=CurTime()] startTime Game time in seconds that the timer started. If you are using `time`, then you shouldn't
-- use this argument
-- @number[opt=startTime + time] finishTime Game time in seconds that the timer should complete at. If you are using `time`,
-- then you shouldn't use this argument
function meta:SetAction(text, time, callback, startTime, finishTime)
if (time and time <= 0) then
if (callback) then
callback(self)
end
return
end
-- Default the time to five seconds.
time = time or 5
startTime = startTime or CurTime()
finishTime = finishTime or (startTime + time)
if (text == false) then
timer.Remove("ixAct"..self:SteamID64())
net.Start("ixActionBarReset")
net.Send(self)
return
end
if (!text) then
net.Start("ixActionBarReset")
net.Send(self)
else
net.Start("ixActionBar")
net.WriteFloat(startTime)
net.WriteFloat(finishTime)
net.WriteString(text)
net.Send(self)
end
-- If we have provided a callback, run it delayed.
if (callback) then
-- Create a timer that runs once with a delay.
timer.Create("ixAct"..self:SteamID64(), time, 1, function()
-- Call the callback if the player is still valid.
if (IsValid(self)) then
callback(self)
end
end)
end
end
--- Opens up a text box on this player's screen for input and returns the result. Remember to sanitize the user's input if
-- it's needed!
-- @realm server
-- @string title Title to display on the panel
-- @string subTitle Subtitle to display on the panel
-- @func callback Function to run when this player enters their input. Callback is ran with the user's input string.
-- @string[opt=nil] default Default value to put in the text box.
-- @usage client:RequestString("Hello", "Please enter your name", function(text)
-- client:ChatPrint("Hello, " .. text)
-- end)
-- -- prints "Hello, " in the player's chat
function meta:RequestString(title, subTitle, callback, default)
local time = math.floor(os.time())
self.ixStrReqs = self.ixStrReqs or {}
self.ixStrReqs[time] = callback
net.Start("ixStringRequest")
net.WriteUInt(time, 32)
net.WriteString(title)
net.WriteString(subTitle)
net.WriteString(default or "")
net.Send(self)
end
--- Sets this player's restricted status.
-- @realm server
-- @bool bState Whether or not to restrict this player
-- @bool bNoMessage Whether or not to suppress the restriction notification
function meta:SetRestricted(bState, bNoMessage)
if (bState) then
self:SetNetVar("restricted", true)
if (bNoMessage) then
self:SetLocalVar("restrictNoMsg", true)
end
self.ixRestrictWeps = self.ixRestrictWeps or {}
for _, v in ipairs(self:GetWeapons()) do
self.ixRestrictWeps[#self.ixRestrictWeps + 1] = {
class = v:GetClass(),
item = v.ixItem,
clip = v:Clip1()
}
v:Remove()
end
hook.Run("OnPlayerRestricted", self)
else
self:SetNetVar("restricted")
if (self:GetLocalVar("restrictNoMsg")) then
self:SetLocalVar("restrictNoMsg")
end
if (self.ixRestrictWeps) then
for _, v in ipairs(self.ixRestrictWeps) do
local weapon = self:Give(v.class, true)
if (v.item) then
weapon.ixItem = v.item
end
weapon:SetClip1(v.clip)
end
self.ixRestrictWeps = nil
end
hook.Run("OnPlayerUnRestricted", self)
end
end
--- Creates a ragdoll entity of this player that will be synced with clients. This does **not** affect the player like
-- `SetRagdolled` does.
-- @realm server
-- @bool[opt=false] bDontSetPlayer Whether or not to avoid setting the ragdoll's owning player
-- @treturn entity Created ragdoll entity
function meta:CreateServerRagdoll(bDontSetPlayer)
local entity = ents.Create("prop_ragdoll")
entity:SetPos(self:GetPos())
entity:SetAngles(self:EyeAngles())
entity:SetModel(self:GetModel())
entity:SetSkin(self:GetSkin())
for i = 0, (self:GetNumBodyGroups() - 1) do
entity:SetBodygroup(i, self:GetBodygroup(i))
end
entity:Spawn()
if (!bDontSetPlayer) then
entity:SetNetVar("player", self)
end
entity:SetCollisionGroup(COLLISION_GROUP_WEAPON)
entity:Activate()
local velocity = self:GetVelocity()
for i = 0, entity:GetPhysicsObjectCount() - 1 do
local physObj = entity:GetPhysicsObjectNum(i)
if (IsValid(physObj)) then
physObj:SetVelocity(velocity)
local index = entity:TranslatePhysBoneToBone(i)
if (index) then
local position, angles = self:GetBonePosition(index)
physObj:SetPos(position)
physObj:SetAngles(angles)
end
end
end
return entity
end
--- Sets this player's ragdoll status.
-- @realm server
-- @bool bState Whether or not to ragdoll this player
-- @number[opt=0] time How long this player should stay ragdolled for. Set to `0` if they should stay ragdolled until they
-- get back up manually
-- @number[opt=5] getUpGrace How much time in seconds to wait before the player is able to get back up manually. Set to
-- the same number as `time` to disable getting up manually entirely
function meta:SetRagdolled(bState, time, getUpGrace)
if (!self:Alive()) then
return
end
getUpGrace = getUpGrace or time or 5
if (bState) then
if (IsValid(self.ixRagdoll)) then
self.ixRagdoll:Remove()
end
local entity = self:CreateServerRagdoll()
entity:CallOnRemove("fixer", function()
if (IsValid(self)) then
self:SetLocalVar("blur", nil)
self:SetLocalVar("ragdoll", nil)
if (!entity.ixNoReset) then
self:SetPos(entity:GetPos())
end
self:SetNoDraw(false)
self:SetNotSolid(false)
self:SetMoveType(MOVETYPE_WALK)
self:SetLocalVelocity(IsValid(entity) and entity.ixLastVelocity or vector_origin)
end
if (IsValid(self) and !entity.ixIgnoreDelete) then
if (entity.ixWeapons) then
for _, v in ipairs(entity.ixWeapons) do
if (v.class) then
local weapon = self:Give(v.class, true)
if (v.item) then
weapon.ixItem = v.item
end
self:SetAmmo(v.ammo, weapon:GetPrimaryAmmoType())
weapon:SetClip1(v.clip)
elseif (v.item and v.invID == v.item.invID) then
v.item:Equip(self, true, true)
self:SetAmmo(v.ammo, self.carryWeapons[v.item.weaponCategory]:GetPrimaryAmmoType())
end
end
end
if (entity.ixActiveWeapon) then
if (self:HasWeapon(entity.ixActiveWeapon)) then
self:SetActiveWeapon(self:GetWeapon(entity.ixActiveWeapon))
else
local weapons = self:GetWeapons()
if (#weapons > 0) then
self:SetActiveWeapon(weapons[1])
end
end
end
if (self:IsStuck()) then
entity:DropToFloor()
self:SetPos(entity:GetPos() + Vector(0, 0, 16))
local positions = ix.util.FindEmptySpace(self, {entity, self})
for _, v in ipairs(positions) do
self:SetPos(v)
if (!self:IsStuck()) then
return
end
end
end
end
end)
self:SetLocalVar("blur", 25)
self.ixRagdoll = entity
entity.ixWeapons = {}
entity.ixPlayer = self
if (getUpGrace) then
entity.ixGrace = CurTime() + getUpGrace
end
if (time and time > 0) then
entity.ixStart = CurTime()
entity.ixFinish = entity.ixStart + time
self:SetAction("@wakingUp", nil, nil, entity.ixStart, entity.ixFinish)
end
if (IsValid(self:GetActiveWeapon())) then
entity.ixActiveWeapon = self:GetActiveWeapon():GetClass()
end
for _, v in ipairs(self:GetWeapons()) do
if (v.ixItem and v.ixItem.Equip and v.ixItem.Unequip) then
entity.ixWeapons[#entity.ixWeapons + 1] = {
item = v.ixItem,
invID = v.ixItem.invID,
ammo = self:GetAmmoCount(v:GetPrimaryAmmoType())
}
v.ixItem:Unequip(self, false)
else
local clip = v:Clip1()
local reserve = self:GetAmmoCount(v:GetPrimaryAmmoType())
entity.ixWeapons[#entity.ixWeapons + 1] = {
class = v:GetClass(),
item = v.ixItem,
clip = clip,
ammo = reserve
}
end
end
self:GodDisable()
self:StripWeapons()
self:SetMoveType(MOVETYPE_OBSERVER)
self:SetNoDraw(true)
self:SetNotSolid(true)
local uniqueID = "ixUnRagdoll" .. self:SteamID()
if (time) then
timer.Create(uniqueID, 0.33, 0, function()
if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then
local velocity = entity:GetVelocity()
entity.ixLastVelocity = velocity
self:SetPos(entity:GetPos())
if (velocity:Length2D() >= 8) then
if (!entity.ixPausing) then
self:SetAction()
entity.ixPausing = true
end
return
elseif (entity.ixPausing) then
self:SetAction("@wakingUp", time)
entity.ixPausing = false
end
time = time - 0.33
if (time <= 0) then
entity:Remove()
end
else
timer.Remove(uniqueID)
end
end)
else
timer.Create(uniqueID, 0.33, 0, function()
if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then
self:SetPos(entity:GetPos())
else
timer.Remove(uniqueID)
end
end)
end
self:SetLocalVar("ragdoll", entity:EntIndex())
hook.Run("OnCharacterFallover", self, entity, true)
elseif (IsValid(self.ixRagdoll)) then
self.ixRagdoll:Remove()
hook.Run("OnCharacterFallover", self, nil, false)
end
end
end
================================================
FILE: gamemode/core/meta/sh_tool.lua
================================================
local TOOL = ix.meta.tool or {}
-- code replicated from gamemodes/sandbox/entities/weapons/gmod_tool/stool.lua
function TOOL:Create()
local object = {}
setmetatable(object, self)
self.__index = self
object.Mode = nil
object.SWEP = nil
object.Owner = nil
object.ClientConVar = {}
object.ServerConVar = {}
object.Objects = {}
object.Stage = 0
object.Message = "start"
object.LastMessage = 0
object.AllowedCVar = 0
return object
end
function TOOL:CreateConVars()
local mode = self:GetMode()
if (CLIENT) then
for cvar, default in pairs(self.ClientConVar) do
CreateClientConVar(mode .. "_" .. cvar, default, true, true)
end
return
end
-- Note: I changed this from replicated because replicated convars don't work when they're created via Lua.
if (SERVER) then
self.AllowedCVar = CreateConVar("toolmode_allow_" .. mode, 1, FCVAR_NOTIFY)
end
end
function TOOL:GetServerInfo(property)
local mode = self:GetMode()
return GetConVarString(mode .. "_" .. property)
end
function TOOL:BuildConVarList()
local mode = self:GetMode()
local convars = {}
for k, v in pairs(self.ClientConVar) do
convars[mode .. "_" .. k] = v
end
return convars
end
function TOOL:GetClientInfo(property)
return self:GetOwner():GetInfo(self:GetMode() .. "_" .. property)
end
function TOOL:GetClientNumber(property, default)
return self:GetOwner():GetInfoNum(self:GetMode() .. "_" .. property, tonumber(default) or 0)
end
function TOOL:Allowed()
if (CLIENT) then
return true
end
return self.AllowedCVar:GetBool()
end
-- Now for all the TOOL redirects
function TOOL:Init()
end
function TOOL:GetMode()
return self.Mode
end
function TOOL:GetSWEP()
return self.SWEP
end
function TOOL:GetOwner()
return self:GetSWEP().Owner or self.Owner
end
function TOOL:GetWeapon()
return self:GetSWEP().Weapon or self.Weapon
end
function TOOL:LeftClick()
return false
end
function TOOL:RightClick()
return false
end
function TOOL:Reload()
self:ClearObjects()
end
function TOOL:Deploy()
self:ReleaseGhostEntity()
return
end
function TOOL:Holster()
self:ReleaseGhostEntity()
return
end
function TOOL:Think()
self:ReleaseGhostEntity()
end
-- Checks the objects before any action is taken
-- This is to make sure that the entities haven't been removed
function TOOL:CheckObjects()
for _, v in pairs(self.Objects) do
if (!v.Ent:IsWorld() and !v.Ent:IsValid()) then
self:ClearObjects()
end
end
end
ix.meta.tool = TOOL
================================================
FILE: gamemode/core/sh_commands.lua
================================================
ix.command.Add("Roll", {
description = "@cmdRoll",
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, maximum)
maximum = math.Clamp(maximum or 100, 0, 1000000)
local value = math.random(0, maximum)
ix.chat.Send(client, "roll", tostring(value), nil, nil, {
max = maximum
})
ix.log.Add(client, "roll", value, maximum)
end
})
ix.command.Add("Event", {
description = "@cmdEvent",
arguments = ix.type.text,
superAdminOnly = true,
OnRun = function(self, client, text)
ix.chat.Send(client, "event", text)
end
})
ix.command.Add("PM", {
description = "@cmdPM",
arguments = {
ix.type.player,
ix.type.text
},
OnRun = function(self, client, target, message)
local voiceMail = target:GetData("vm")
if (voiceMail and voiceMail:find("%S")) then
return target:GetName()..": "..voiceMail
end
if ((client.ixNextPM or 0) < CurTime()) then
ix.chat.Send(client, "pm", message, false, {client, target}, {target = target})
client.ixNextPM = CurTime() + 0.5
target.ixLastPM = client
end
end
})
ix.command.Add("Reply", {
description = "@cmdReply",
arguments = ix.type.text,
OnRun = function(self, client, message)
local target = client.ixLastPM
if (IsValid(target) and (client.ixNextPM or 0) < CurTime()) then
ix.chat.Send(client, "pm", message, false, {client, target}, {target = target})
client.ixNextPM = CurTime() + 0.5
end
end
})
ix.command.Add("SetVoicemail", {
description = "@cmdSetVoicemail",
arguments = bit.bor(ix.type.text, ix.type.optional),
OnRun = function(self, client, message)
if (isstring(message) and message:find("%S")) then
client:SetData("vm", message:utf8sub(1, 240))
return "@vmSet"
else
client:SetData("vm")
return "@vmRem"
end
end
})
ix.command.Add("CharGiveFlag", {
description = "@cmdCharGiveFlag",
privilege = "Manage Character Flags",
superAdminOnly = true,
arguments = {
ix.type.character,
bit.bor(ix.type.string, ix.type.optional)
},
OnRun = function(self, client, target, flags)
-- show string request if no flags are specified
if (!flags) then
local available = ""
-- sort and display flags the character already has
for k, _ in SortedPairs(ix.flag.list) do
if (!target:HasFlags(k)) then
available = available .. k
end
end
return client:RequestString("@flagGiveTitle", "@cmdCharGiveFlag", function(text)
ix.command.Run(client, "CharGiveFlag", {target:GetName(), text})
end, available)
end
target:GiveFlags(flags)
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("flagGive", client:GetName(), target:GetName(), flags)
end
end
end
})
ix.command.Add("CharTakeFlag", {
description = "@cmdCharTakeFlag",
privilege = "Manage Character Flags",
superAdminOnly = true,
arguments = {
ix.type.character,
bit.bor(ix.type.string, ix.type.optional)
},
OnRun = function(self, client, target, flags)
if (!flags) then
return client:RequestString("@flagTakeTitle", "@cmdCharTakeFlag", function(text)
ix.command.Run(client, "CharTakeFlag", {target:GetName(), text})
end, target:GetFlags())
end
target:TakeFlags(flags)
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("flagTake", client:GetName(), flags, target:GetName())
end
end
end
})
ix.command.Add("ToggleRaise", {
description = "@cmdToggleRaise",
OnRun = function(self, client, arguments)
if (!timer.Exists("ixToggleRaise" .. client:SteamID())) then
timer.Create("ixToggleRaise" .. client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function()
client:ToggleWepRaised()
end)
end
end
})
ix.command.Add("CharSetModel", {
description = "@cmdCharSetModel",
superAdminOnly = true,
arguments = {
ix.type.character,
ix.type.string
},
OnRun = function(self, client, target, model)
target:SetModel(model)
target:GetPlayer():SetupHands()
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("cChangeModel", client:GetName(), target:GetName(), model)
end
end
end
})
ix.command.Add("CharSetSkin", {
description = "@cmdCharSetSkin",
adminOnly = true,
arguments = {
ix.type.character,
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, target, skin)
target:SetData("skin", skin)
target:GetPlayer():SetSkin(skin or 0)
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("cChangeSkin", client:GetName(), target:GetName(), skin or 0)
end
end
end
})
ix.command.Add("CharSetBodygroup", {
description = "@cmdCharSetBodygroup",
adminOnly = true,
arguments = {
ix.type.character,
ix.type.string,
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, target, bodygroup, value)
local index = target:GetPlayer():FindBodygroupByName(bodygroup)
if (index > -1) then
if (value and value < 1) then
value = nil
end
local groups = target:GetData("groups", {})
groups[index] = value
target:SetData("groups", groups)
target:GetPlayer():SetBodygroup(index, value or 0)
ix.util.NotifyLocalized("cChangeGroups", nil, client:GetName(), target:GetName(), bodygroup, value or 0)
else
return "@invalidArg", 2
end
end
})
ix.command.Add("CharSetAttribute", {
description = "@cmdCharSetAttribute",
privilege = "Manage Character Attributes",
adminOnly = true,
arguments = {
ix.type.character,
ix.type.string,
ix.type.number
},
OnRun = function(self, client, target, attributeName, level)
for k, v in pairs(ix.attributes.list) do
if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then
target:SetAttrib(k, math.abs(level))
return "@attributeSet", target:GetName(), L(v.name, client), math.abs(level)
end
end
return "@attributeNotFound"
end
})
ix.command.Add("CharAddAttribute", {
description = "@cmdCharAddAttribute",
privilege = "Manage Character Attributes",
adminOnly = true,
arguments = {
ix.type.character,
ix.type.string,
ix.type.number
},
OnRun = function(self, client, target, attributeName, level)
for k, v in pairs(ix.attributes.list) do
if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then
target:UpdateAttrib(k, math.abs(level))
return "@attributeUpdate", target:GetName(), L(v.name, client), math.abs(level)
end
end
return "@attributeNotFound"
end
})
ix.command.Add("CharSetName", {
description = "@cmdCharSetName",
adminOnly = true,
arguments = {
ix.type.character,
bit.bor(ix.type.text, ix.type.optional)
},
OnRun = function(self, client, target, newName)
-- display string request panel if no name was specified
if (newName:len() == 0) then
return client:RequestString("@chgName", "@chgNameDesc", function(text)
ix.command.Run(client, "CharSetName", {target:GetName(), text})
end, target:GetName())
end
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("cChangeName", client:GetName(), target:GetName(), newName)
end
end
target:SetName(newName:gsub("#", "#"))
end
})
ix.command.Add("CharGiveItem", {
description = "@cmdCharGiveItem",
superAdminOnly = true,
arguments = {
ix.type.character,
ix.type.string,
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, target, item, amount)
local uniqueID = item:lower()
if (!ix.item.list[uniqueID]) then
for k, v in SortedPairs(ix.item.list) do
if (ix.util.StringMatches(v.name, uniqueID)) then
uniqueID = k
break
end
end
end
amount = amount or 1
local bSuccess, error = target:GetInventory():Add(uniqueID, amount)
if (bSuccess) then
target:GetPlayer():NotifyLocalized("itemCreated")
if (target != client:GetCharacter()) then
return "@itemCreated"
end
else
return "@" .. tostring(error)
end
end
})
ix.command.Add("CharKick", {
description = "@cmdCharKick",
adminOnly = true,
arguments = ix.type.character,
OnRun = function(self, client, target)
target:Save(function()
target:Kick()
end)
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("charKick", client:GetName(), target:GetName())
end
end
end
})
ix.command.Add("CharBan", {
description = "@cmdCharBan",
privilege = "Ban Character",
arguments = {
ix.type.character,
bit.bor(ix.type.number, ix.type.optional)
},
adminOnly = true,
OnRun = function(self, client, target, minutes)
if (minutes) then
minutes = minutes * 60
end
target:Ban(minutes)
target:Save()
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("charBan", client:GetName(), target:GetName())
end
end
end
})
ix.command.Add("CharUnban", {
description = "@cmdCharUnban",
privilege = "Ban Character",
arguments = ix.type.text,
adminOnly = true,
OnRun = function(self, client, name)
if ((client.ixNextSearch or 0) >= CurTime()) then
return L("charSearching", client)
end
for _, v in pairs(ix.char.loaded) do
if (ix.util.StringMatches(v:GetName(), name)) then
if (v:GetData("banned")) then
v:SetData("banned")
else
return "@charNotBanned"
end
for _, v2 in player.Iterator() do
if (self:OnCheckAccess(v2) or v2 == v:GetPlayer()) then
v2:NotifyLocalized("charUnBan", client:GetName(), v:GetName())
end
end
return
end
end
client.ixNextSearch = CurTime() + 15
local query = mysql:Select("ix_characters")
query:Select("id")
query:Select("name")
query:Select("data")
query:WhereLike("name", name)
query:Limit(1)
query:Callback(function(result)
if (istable(result) and #result > 0) then
local characterID = tonumber(result[1].id)
local data = util.JSONToTable(result[1].data or "[]")
name = result[1].name
client.ixNextSearch = 0
if (!data.banned) then
return client:NotifyLocalized("charNotBanned")
end
data.banned = nil
local updateQuery = mysql:Update("ix_characters")
updateQuery:Update("data", util.TableToJSON(data))
updateQuery:Where("id", characterID)
updateQuery:Execute()
for _, v in player.Iterator() do
if (self:OnCheckAccess(v)) then
v:NotifyLocalized("charUnBan", client:GetName(), name)
end
end
end
end)
query:Execute()
end
})
do
hook.Add("InitializedConfig", "ixMoneyCommands", function()
local MONEY_NAME = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "")
ix.command.Add("Give" .. MONEY_NAME, {
alias = {"GiveMoney"},
description = "@cmdGiveMoney",
arguments = ix.type.number,
OnRun = function(self, client, amount)
amount = math.floor(amount)
if (amount <= 0) then
return L("invalidArg", client, 1)
end
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local target = util.TraceLine(data).Entity
if (IsValid(target) and target:IsPlayer() and target:GetCharacter()) then
if (!client:GetCharacter():HasMoney(amount)) then
return
end
target:GetCharacter():GiveMoney(amount)
client:GetCharacter():TakeMoney(amount)
target:NotifyLocalized("moneyTaken", ix.currency.Get(amount))
client:NotifyLocalized("moneyGiven", ix.currency.Get(amount))
end
end
})
ix.command.Add("CharSet" .. MONEY_NAME, {
alias = {"CharSetMoney"},
description = "@cmdCharSetMoney",
superAdminOnly = true,
arguments = {
ix.type.character,
ix.type.number
},
OnRun = function(self, client, target, amount)
amount = math.Round(amount)
if (amount <= 0) then
return "@invalidArg", 2
end
target:SetMoney(amount)
client:NotifyLocalized("setMoney", target:GetName(), ix.currency.Get(amount))
end
})
ix.command.Add("Drop" .. MONEY_NAME, {
alias = {"DropMoney"},
description = "@cmdDropMoney",
arguments = ix.type.number,
OnRun = function(self, client, amount)
amount = math.Round(amount)
local minDropAmount = ix.config.Get("minMoneyDropAmount", 1)
if (amount < minDropAmount) then
return "@belowMinMoneyDrop", minDropAmount
end
if (!client:GetCharacter():HasMoney(amount)) then
return "@insufficientMoney"
end
client:GetCharacter():TakeMoney(amount)
local money = ix.currency.Spawn(client, amount)
money.ixCharID = client:GetCharacter():GetID()
money.ixSteamID = client:SteamID()
end
})
end)
end
ix.command.Add("PlyWhitelist", {
description = "@cmdPlyWhitelist",
privilege = "Manage Character Whitelist",
superAdminOnly = true,
arguments = {
ix.type.player,
ix.type.text
},
OnRun = function(self, client, target, name)
if (name == "") then
return "@invalidArg", 2
end
local faction = ix.faction.teams[name]
if (!faction) then
for _, v in ipairs(ix.faction.indices) do
if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then
faction = v
break
end
end
end
if (faction) then
if (target:SetWhitelisted(faction.index, true)) then
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target) then
v:NotifyLocalized("whitelist", client:GetName(), target:GetName(), L(faction.name, v))
end
end
end
else
return "@invalidFaction"
end
end
})
ix.command.Add("CharGetUp", {
description = "@cmdCharGetUp",
OnRun = function(self, client, arguments)
local entity = client.ixRagdoll
if (IsValid(entity) and entity.ixGrace and entity.ixGrace < CurTime() and
entity:GetVelocity():Length2D() < 8 and !entity.ixWakingUp) then
entity.ixWakingUp = true
entity:CallOnRemove("CharGetUp", function()
client:SetAction()
end)
client:SetAction("@gettingUp", 5, function()
if (!IsValid(entity)) then
return
end
hook.Run("OnCharacterGetup", client, entity)
entity:Remove()
end)
end
end
})
ix.command.Add("PlyUnwhitelist", {
description = "@cmdPlyUnwhitelist",
privilege = "Manage Character Whitelist",
superAdminOnly = true,
arguments = {
ix.type.string,
ix.type.text
},
OnRun = function(self, client, target, name)
local faction = ix.faction.teams[name]
if (!faction) then
for _, v in ipairs(ix.faction.indices) do
if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then
faction = v
break
end
end
end
if (faction) then
local targetPlayer = ix.util.FindPlayer(target)
if (IsValid(targetPlayer) and targetPlayer:SetWhitelisted(faction.index, false)) then
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == targetPlayer) then
v:NotifyLocalized("unwhitelist", client:GetName(), targetPlayer:GetName(), L(faction.name, v))
end
end
else
local steamID64 = util.SteamIDTo64(target)
local query = mysql:Select("ix_players")
query:Select("data")
query:Where("steamid", steamID64)
query:Limit(1)
query:Callback(function(result)
if (istable(result) and #result > 0) then
local data = util.JSONToTable(result[1].data or "[]")
local whitelists = data.whitelists and data.whitelists[Schema.folder]
if (!whitelists or !whitelists[faction.uniqueID]) then
return
end
whitelists[faction.uniqueID] = nil
local updateQuery = mysql:Update("ix_players")
updateQuery:Update("data", util.TableToJSON(data))
updateQuery:Where("steamid", steamID64)
updateQuery:Execute()
for _, v in player.Iterator() do
if (self:OnCheckAccess(v)) then
v:NotifyLocalized("unwhitelist", client:GetName(), target, L(faction.name, v))
end
end
end
end)
query:Execute()
end
else
return "@invalidFaction"
end
end
})
ix.command.Add("CharFallOver", {
description = "@cmdCharFallOver",
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, time)
if (!client:Alive() or client:GetMoveType() == MOVETYPE_NOCLIP) then
return "@notNow"
end
if (time and time > 0) then
time = math.Clamp(time, 1, 60)
end
if (!IsValid(client.ixRagdoll)) then
client:SetRagdolled(true, time)
end
end
})
ix.command.Add("BecomeClass", {
description = "@cmdBecomeClass",
arguments = ix.type.text,
OnRun = function(self, client, class)
local character = client:GetCharacter()
if (character) then
local num = isnumber(tonumber(class)) and tonumber(class) or -1
if (ix.class.list[num]) then
local v = ix.class.list[num]
if (character:JoinClass(num)) then
return "@becomeClass", L(v.name, client)
else
return "@becomeClassFail", L(v.name, client)
end
else
for k, v in ipairs(ix.class.list) do
if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(L(v.name, client), class)) then
if (character:JoinClass(k)) then
return "@becomeClass", L(v.name, client)
else
return "@becomeClassFail", L(v.name, client)
end
end
end
end
return "@invalid", L("class", client)
else
return "@illegalAccess"
end
end
})
ix.command.Add("CharDesc", {
description = "@cmdCharDesc",
arguments = bit.bor(ix.type.text, ix.type.optional),
OnRun = function(self, client, description)
if (!description:find("%S")) then
return client:RequestString("@cmdCharDescTitle", "@cmdCharDescDescription", function(text)
ix.command.Run(client, "CharDesc", {text})
end, client:GetCharacter():GetDescription())
end
local info = ix.char.vars.description
local result, fault, count = info:OnValidate(description)
if (result == false) then
return "@" .. fault, count
end
client:GetCharacter():SetDescription(description)
return "@descChanged"
end
})
ix.command.Add("PlyTransfer", {
description = "@cmdPlyTransfer",
adminOnly = true,
arguments = {
ix.type.character,
ix.type.text
},
OnRun = function(self, client, target, name)
local faction = ix.faction.teams[name]
if (!faction) then
for _, v in pairs(ix.faction.indices) do
if (ix.util.StringMatches(L(v.name, client), name)) then
faction = v
break
end
end
end
if (faction) then
local bHasWhitelist = target:GetPlayer():HasWhitelist(faction.index)
if (bHasWhitelist) then
target.vars.faction = faction.uniqueID
target:SetFaction(faction.index)
if (faction.OnTransferred) then
faction:OnTransferred(target)
end
for _, v in player.Iterator() do
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
v:NotifyLocalized("cChangeFaction", client:GetName(), target:GetName(), L(faction.name, v))
end
end
else
return "@charNotWhitelisted", target:GetName(), L(faction.name, client)
end
else
return "@invalidFaction"
end
end
})
ix.command.Add("CharSetClass", {
description = "@cmdCharSetClass",
adminOnly = true,
arguments = {
ix.type.character,
ix.type.text
},
OnRun = function(self, client, target, class)
local classTable
for _, v in ipairs(ix.class.list) do
if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(v.name, class)) then
classTable = v
end
end
if (classTable) then
local oldClass = target:GetClass()
local targetPlayer = target:GetPlayer()
if (targetPlayer:Team() == classTable.faction) then
target:SetClass(classTable.index)
hook.Run("PlayerJoinedClass", targetPlayer, classTable.index, oldClass)
targetPlayer:NotifyLocalized("becomeClass", L(classTable.name, targetPlayer))
-- only send second notification if the character isn't setting their own class
if (client != targetPlayer) then
return "@setClass", target:GetName(), L(classTable.name, client)
end
else
return "@invalidClassFaction"
end
else
return "@invalidClass"
end
end
})
ix.command.Add("MapRestart", {
description = "@cmdMapRestart",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, delay)
delay = delay or 10
ix.util.NotifyLocalized("mapRestarting", nil, delay)
timer.Simple(delay, function()
RunConsoleCommand("changelevel", game.GetMap())
end)
end
})
================================================
FILE: gamemode/core/sh_config.lua
================================================
--- Helper library for creating/setting config options.
-- @module ix.config
ix.config = ix.config or {}
ix.config.stored = ix.config.stored or {}
if (SERVER) then
util.AddNetworkString("ixConfigList")
util.AddNetworkString("ixConfigSet")
util.AddNetworkString("ixConfigRequestUnloadedList")
util.AddNetworkString("ixConfigUnloadedList")
util.AddNetworkString("ixConfigPluginToggle")
ix.config.server = ix.yaml.Read("gamemodes/helix/helix.yml") or {}
end
CAMI.RegisterPrivilege({
Name = "Helix - Manage Config",
MinAccess = "superadmin"
})
--- Creates a config option with the given information.
-- @realm shared
-- @string key Unique ID of the config
-- @param value Default value that this config will have
-- @string description Description of the config
-- @func[opt=nil] callback Function to call when config is changed
-- @tab[opt=nil] data Additional settings for this config option
-- @bool[opt=false] bNoNetworking Whether or not to prevent networking the config
-- @bool[opt=false] bSchemaOnly Whether or not the config is for the schema only
function ix.config.Add(key, value, description, callback, data, bNoNetworking, bSchemaOnly)
data = istable(data) and data or {}
local oldConfig = ix.config.stored[key]
local type = data.type or ix.util.GetTypeFromValue(value)
if (!type) then
ErrorNoHalt("attempted to add config with invalid type\n")
return
end
local default = value
data.type = nil
-- using explicit nil comparisons so we don't get caught by a config's value being `false`
if (oldConfig != nil) then
if (oldConfig.value != nil) then
value = oldConfig.value
end
if (oldConfig.default != nil) then
default = oldConfig.default
end
end
ix.config.stored[key] = {
type = type,
data = data,
value = value,
default = default,
description = description,
bNoNetworking = bNoNetworking,
global = !bSchemaOnly,
callback = callback,
hidden = data.hidden or nil
}
end
--- Sets the default value for a config option.
-- @realm shared
-- @string key Unique ID of the config
-- @param value Default value for the config option
function ix.config.SetDefault(key, value)
local config = ix.config.stored[key]
if (config) then
config.default = value
else
-- set up dummy config if we're setting default of config that doesn't exist yet (i.e schema setting framework default)
ix.config.stored[key] = {
value = value,
default = value
}
end
end
function ix.config.ForceSet(key, value, noSave)
local config = ix.config.stored[key]
if (config) then
config.value = value
end
if (noSave) then
ix.config.Save()
end
end
--- Sets the value of a config option.
-- @realm shared
-- @string key Unique ID of the config
-- @param value New value to assign to the config
function ix.config.Set(key, value)
local config = ix.config.stored[key]
if (config) then
local oldValue = value
config.value = value
if (SERVER) then
if (!config.bNoNetworking) then
net.Start("ixConfigSet")
net.WriteString(key)
net.WriteType(value)
net.Broadcast()
end
if (config.callback) then
config.callback(oldValue, value)
end
ix.config.Save()
end
end
end
--- Retrieves a value of a config option. If it is not set, it'll return the default that you've specified.
-- @realm shared
-- @string key Unique ID of the config
-- @param default Default value to return if the config is not set
-- @return Value associated with the key, or the default that was given if it doesn't exist
function ix.config.Get(key, default)
local config = ix.config.stored[key]
-- ensure we aren't accessing a dummy value
if (config and config.type) then
if (config.value != nil) then
return config.value
elseif (config.default != nil) then
return config.default
end
end
return default
end
--- Loads all saved config options from disk.
-- @realm shared
-- @internal
function ix.config.Load()
if (SERVER) then
local globals = ix.data.Get("config", nil, true, true)
local data = ix.data.Get("config", nil, false, true)
if (globals) then
for k, v in pairs(globals) do
ix.config.stored[k] = ix.config.stored[k] or {}
ix.config.stored[k].value = v
end
end
if (data) then
for k, v in pairs(data) do
ix.config.stored[k] = ix.config.stored[k] or {}
ix.config.stored[k].value = v
end
end
end
ix.util.Include("helix/gamemode/config/sh_config.lua")
if (SERVER or !IX_RELOADED) then
hook.Run("InitializedConfig")
end
end
if (SERVER) then
function ix.config.GetChangedValues()
local data = {}
for k, v in pairs(ix.config.stored) do
if (v.default != v.value) then
data[k] = v.value
end
end
return data
end
function ix.config.Send(client)
net.Start("ixConfigList")
net.WriteTable(ix.config.GetChangedValues())
net.Send(client)
end
--- Saves all config options to disk.
-- @realm server
-- @internal
function ix.config.Save()
local globals = {}
local data = {}
for k, v in pairs(ix.config.GetChangedValues()) do
if (ix.config.stored[k].global) then
globals[k] = v
else
data[k] = v
end
end
-- Global and schema data set respectively.
ix.data.Set("config", globals, true, true)
ix.data.Set("config", data, false, true)
end
net.Receive("ixConfigSet", function(length, client)
local key = net.ReadString()
local value = net.ReadType()
if (CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil) and
type(ix.config.stored[key].default) == type(value)) then
ix.config.Set(key, value)
if (ix.util.IsColor(value)) then
value = string.format("[%d, %d, %d]", value.r, value.g, value.b)
elseif (istable(value)) then
local value2 = "["
local count = table.Count(value)
local i = 1
for _, v in SortedPairs(value) do
value2 = value2 .. v .. (i == count and "]" or ", ")
i = i + 1
end
value = value2
elseif (isstring(value)) then
value = string.format("\"%s\"", tostring(value))
elseif (isbool(value)) then
value = string.format("[%s]", tostring(value))
end
ix.util.NotifyLocalized("cfgSet", nil, client:Name(), key, tostring(value))
ix.log.Add(client, "cfgSet", key, value)
end
end)
net.Receive("ixConfigRequestUnloadedList", function(length, client)
if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then
return
end
net.Start("ixConfigUnloadedList")
net.WriteTable(ix.plugin.unloaded)
net.Send(client)
end)
net.Receive("ixConfigPluginToggle", function(length, client)
if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then
return
end
local uniqueID = net.ReadString()
local bUnloaded = !!ix.plugin.unloaded[uniqueID]
local bShouldEnable = net.ReadBool()
if ((bShouldEnable and bUnloaded) or (!bShouldEnable and !bUnloaded)) then
ix.plugin.SetUnloaded(uniqueID, !bShouldEnable) -- flip bool since we're setting unloaded, not enabled
ix.util.NotifyLocalized(bShouldEnable and "pluginLoaded" or "pluginUnloaded", nil, client:GetName(), uniqueID)
ix.log.Add(client, bShouldEnable and "pluginLoaded" or "pluginUnloaded", uniqueID)
net.Start("ixConfigPluginToggle")
net.WriteString(uniqueID)
net.WriteBool(bShouldEnable)
net.Broadcast()
end
end)
else
net.Receive("ixConfigList", function()
local data = net.ReadTable()
for k, v in pairs(data) do
if (ix.config.stored[k]) then
ix.config.stored[k].value = v
end
end
hook.Run("InitializedConfig", data)
end)
net.Receive("ixConfigSet", function()
local key = net.ReadString()
local value = net.ReadType()
local config = ix.config.stored[key]
if (config) then
if (config.callback) then
config.callback(config.value, value)
end
config.value = value
local properties = ix.gui.properties
if (IsValid(properties)) then
local row = properties:GetCategory(L(config.data and config.data.category or "misc")):GetRow(key)
if (IsValid(row)) then
if (istable(value) and value.r and value.g and value.b) then
value = Vector(value.r / 255, value.g / 255, value.b / 255)
end
row:SetValue(value)
end
end
end
end)
net.Receive("ixConfigUnloadedList", function()
ix.plugin.unloaded = net.ReadTable()
ix.gui.bReceivedUnloadedPlugins = true
if (IsValid(ix.gui.pluginManager)) then
ix.gui.pluginManager:UpdateUnloaded()
end
end)
net.Receive("ixConfigPluginToggle", function()
local uniqueID = net.ReadString()
local bEnabled = net.ReadBool()
if (bEnabled) then
ix.plugin.unloaded[uniqueID] = false
else
ix.plugin.unloaded[uniqueID] = true
end
if (IsValid(ix.gui.pluginManager)) then
ix.gui.pluginManager:UpdatePlugin(uniqueID, bEnabled)
end
end)
hook.Add("CreateMenuButtons", "ixConfig", function(tabs)
if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Config", nil)) then
return
end
tabs["config"] = {
Create = function(info, container)
container.panel = container:Add("ixConfigManager")
end,
OnSelected = function(info, container)
container.panel.searchEntry:RequestFocus()
end,
Sections = {
plugins = {
Create = function(info, container)
ix.gui.pluginManager = container:Add("ixPluginManager")
end,
OnSelected = function(info, container)
ix.gui.pluginManager.searchEntry:RequestFocus()
end
}
}
}
end)
end
================================================
FILE: gamemode/core/sh_data.lua
================================================
--- Helper library for reading/writing files to the data folder.
-- @module ix.data
ix.data = ix.data or {}
ix.data.stored = ix.data.stored or {}
-- Create a folder to store data in.
file.CreateDir("helix")
--- Populates a file in the `data/helix` folder with some serialized data.
-- @realm shared
-- @string key Name of the file to save
-- @param value Some sort of data to save
-- @bool[opt=false] bGlobal Whether or not to write directly to the `data/helix` folder, or the `data/helix/schema` folder,
-- where `schema` is the name of the current schema.
-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and save in the schema folder, rather than
-- `data/helix/schema/map`, where `map` is the name of the current map.
function ix.data.Set(key, value, bGlobal, bIgnoreMap)
-- Get the base path to write to.
local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/")
-- Create the schema folder if the data is not global.
if (!bGlobal) then
file.CreateDir("helix/" .. Schema.folder .. "/")
end
-- If we're not ignoring the map, create a folder for the map.
file.CreateDir(path)
-- Write the data using JSON encoding.
file.Write(path .. key .. ".txt", util.TableToJSON({value}))
-- Cache the data value here.
ix.data.stored[key] = value
return path
end
--- Retrieves the contents of a saved file in the `data/helix` folder.
-- @realm shared
-- @string key Name of the file to load
-- @param default Value to return if the file could not be loaded successfully
-- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder,
-- where `schema` is the name of the current schema.
-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and load from the schema folder, rather than
-- `data/helix/schema/map`, where `map` is the name of the current map.
-- @bool[opt=false] bRefresh Whether or not to skip the cache and forcefully load from disk.
-- @return Value associated with the key, or the default that was given if it doesn't exists
function ix.data.Get(key, default, bGlobal, bIgnoreMap, bRefresh)
-- If it exists in the cache, return the cached value so it is faster.
if (!bRefresh) then
local stored = ix.data.stored[key]
if (stored != nil) then
return stored
end
end
-- Get the path to read from.
local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/")
-- Read the data from a local file.
local contents = file.Read(path .. key .. ".txt", "DATA")
if (contents and contents != "") then
local status, decoded = pcall(util.JSONToTable, contents)
if (status and decoded) then
local value = decoded[1]
if (value != nil) then
return value
end
end
-- Backwards compatibility.
-- This may be removed in the future.
status, decoded = pcall(pon.decode, contents)
if (status and decoded) then
local value = decoded[1]
if (value != nil) then
return value
end
end
end
return default
end
--- Deletes the contents of a saved file in the `data/helix` folder.
-- @realm shared
-- @string key Name of the file to delete
-- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder,
-- where `schema` is the name of the current schema.
-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and delete from the schema folder, rather than
-- `data/helix/schema/map`, where `map` is the name of the current map.
-- @treturn bool Whether or not the deletion has succeeded
function ix.data.Delete(key, bGlobal, bIgnoreMap)
-- Get the path to read from.
local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/")
-- Read the data from a local file.
local contents = file.Read(path .. key .. ".txt", "DATA")
if (contents and contents != "") then
file.Delete(path .. key .. ".txt")
ix.data.stored[key] = nil
return true
end
return false
end
if (SERVER) then
timer.Create("ixSaveData", 600, 0, function()
hook.Run("SaveData")
end)
end
================================================
FILE: gamemode/core/sh_util.lua
================================================
--- Various useful helper functions.
-- @module ix.util
ix.type = ix.type or {
[2] = "string",
[4] = "text",
[8] = "number",
[16] = "player",
[32] = "steamid",
[64] = "character",
[128] = "bool",
[1024] = "color",
[2048] = "vector",
string = 2,
text = 4,
number = 8,
player = 16,
steamid = 32,
character = 64,
bool = 128,
color = 1024,
vector = 2048,
optional = 256,
array = 512
}
ix.blurRenderQueue = {}
--- Includes a lua file based on the prefix of the file. This will automatically call `include` and `AddCSLuaFile` based on the
-- current realm. This function should always be called shared to ensure that the client will receive the file from the server.
-- @realm shared
-- @string fileName Path of the Lua file to include. The path is relative to the file that is currently running this function
-- @string[opt] realm Realm that this file should be included in. You should usually ignore this since it
-- will be automatically be chosen based on the `SERVER` and `CLIENT` globals. This value should either be `"server"` or
-- `"client"` if it is filled in manually
function ix.util.Include(fileName, realm)
if (!fileName) then
error("[Helix] No file name specified for including.")
end
-- Only include server-side if we're on the server.
if ((realm == "server" or fileName:find("sv_")) and SERVER) then
return include(fileName)
-- Shared is included by both server and client.
elseif (realm == "shared" or fileName:find("shared.lua") or fileName:find("sh_")) then
if (SERVER) then
-- Send the file to the client if shared so they can run it.
AddCSLuaFile(fileName)
end
return include(fileName)
-- File is sent to client, included on client.
elseif (realm == "client" or fileName:find("cl_")) then
if (SERVER) then
AddCSLuaFile(fileName)
else
return include(fileName)
end
end
end
--- Includes multiple files in a directory.
-- @realm shared
-- @string directory Directory to include files from
-- @bool[opt] bFromLua Whether or not to search from the base `lua/` folder, instead of contextually basing from `schema/`
-- or `gamemode/`
-- @see ix.util.Include
function ix.util.IncludeDir(directory, bFromLua)
-- By default, we include relatively to Helix.
local baseDir = "helix"
-- If we're in a schema, include relative to the schema.
if (Schema and Schema.folder and Schema.loading) then
baseDir = Schema.folder.."/schema/"
else
baseDir = baseDir.."/gamemode/"
end
-- Find all of the files within the directory.
for _, v in ipairs(file.Find((bFromLua and "" or baseDir)..directory.."/*.lua", "LUA")) do
-- Include the file from the prefix.
ix.util.Include(directory.."/"..v)
end
end
--- Removes the realm prefix from a file name. The returned string will be unchanged if there is no prefix found.
-- @realm shared
-- @string name String to strip prefix from
-- @treturn string String stripped of prefix
-- @usage print(ix.util.StripRealmPrefix("sv_init.lua"))
-- > init.lua
function ix.util.StripRealmPrefix(name)
local prefix = name:sub(1, 3)
return (prefix == "sh_" or prefix == "sv_" or prefix == "cl_") and name:sub(4) or name
end
--- Returns `true` if the given input is a color table. This is necessary since the engine `IsColor` function only checks for
-- color metatables - which are not used for regular Lua color types.
-- @realm shared
-- @param input Input to check
-- @treturn bool Whether or not the input is a color
function ix.util.IsColor(input)
return istable(input) and
isnumber(input.a) and isnumber(input.g) and isnumber(input.b) and (input.a and isnumber(input.a) or input.a == nil)
end
--- Returns a dimmed version of the given color by the given scale.
-- @realm shared
-- @color color Color to dim
-- @number multiplier What to multiply the red, green, and blue values by
-- @number[opt=255] alpha Alpha to use in dimmed color
-- @treturn color Dimmed color
-- @usage print(ix.util.DimColor(Color(100, 100, 100, 255), 0.5))
-- > 50 50 50 255
function ix.util.DimColor(color, multiplier, alpha)
return Color(color.r * multiplier, color.g * multiplier, color.b * multiplier, alpha or 255)
end
--- Sanitizes an input value with the given type. This function ensures that a valid type is always returned. If a valid value
-- could not be found, it will return the default value for the type. This only works for simple types - e.g it does not work
-- for player, character, or Steam ID types.
-- @realm shared
-- @ixtype type Type to check for
-- @param input Value to sanitize
-- @return Sanitized value
-- @see ix.type
-- @usage print(ix.util.SanitizeType(ix.type.number, "123"))
-- > 123
-- print(ix.util.SanitizeType(ix.type.bool, 1))
-- > true
function ix.util.SanitizeType(type, input)
if (type == ix.type.string) then
return tostring(input)
elseif (type == ix.type.text) then
return tostring(input)
elseif (type == ix.type.number) then
return tonumber(input or 0) or 0
elseif (type == ix.type.bool) then
return tobool(input)
elseif (type == ix.type.color) then
return istable(input) and
Color(tonumber(input.r) or 255, tonumber(input.g) or 255, tonumber(input.b) or 255, tonumber(input.a) or 255) or
color_white
elseif (type == ix.type.vector) then
return isvector(input) and input or vector_origin
elseif (type == ix.type.array) then
return input
else
error("attempted to sanitize " .. (ix.type[type] and ("invalid type " .. ix.type[type]) or "unknown type " .. type))
end
end
do
local typeMap = {
string = ix.type.string,
number = ix.type.number,
Player = ix.type.player,
boolean = ix.type.bool,
Vector = ix.type.vector
}
local tableMap = {
[ix.type.character] = function(value)
return getmetatable(value) == ix.meta.character
end,
[ix.type.color] = function(value)
return ix.util.IsColor(value)
end,
[ix.type.steamid] = function(value)
return isstring(value) and (value:match("STEAM_(%d+):(%d+):(%d+)")) != nil
end
}
--- Returns the `ix.type` of the given value.
-- @realm shared
-- @param value Value to get the type of
-- @treturn ix.type Type of value
-- @see ix.type
-- @usage print(ix.util.GetTypeFromValue("hello"))
-- > 2 -- i.e the value of ix.type.string
function ix.util.GetTypeFromValue(value)
local result = typeMap[type(value)]
if (result) then
return result
end
if (istable(value)) then
for k, v in pairs(tableMap) do
if (v(value)) then
return k
end
end
end
end
end
function ix.util.Bind(self, callback)
return function(_, ...)
return callback(self, ...)
end
end
-- Returns the address:port of the server.
function ix.util.GetAddress()
return game.GetIPAddress()
end
--- Returns a cached copy of the given material, or creates and caches one if it doesn't exist. This is a quick helper function
-- if you aren't locally storing a `Material()` call.
-- @realm shared
-- @string materialPath Path to the material
-- @treturn[1] material The cached material
-- @treturn[2] nil If the material doesn't exist in the filesystem
function ix.util.GetMaterial(materialPath)
-- Cache the material.
ix.util.cachedMaterials = ix.util.cachedMaterials or {}
ix.util.cachedMaterials[materialPath] = ix.util.cachedMaterials[materialPath] or Material(materialPath)
return ix.util.cachedMaterials[materialPath]
end
--- Attempts to find a player by matching their name or Steam ID.
-- @realm shared
-- @string identifier Search query
-- @bool[opt=false] bAllowPatterns Whether or not to accept Lua patterns in `identifier`
-- @treturn player Player that matches the given search query - this will be `nil` if a player could not be found
function ix.util.FindPlayer(identifier, bAllowPatterns)
if (#identifier == 0) then return end
if (string.find(identifier, "STEAM_(%d+):(%d+):(%d+)")) then
return player.GetBySteamID(identifier)
end
if (!bAllowPatterns) then
identifier = string.PatternSafe(identifier)
end
for _, v in player.Iterator() do
if (ix.util.StringMatches(v:Name(), identifier)) then
return v
end
end
end
--- Checks to see if two strings are equivalent using a fuzzy manner. Both strings will be lowered, and will return `true` if
-- the strings are identical, or if `b` is a substring of `a`.
-- @realm shared
-- @string a First string to check
-- @string b Second string to check
-- @treturn bool Whether or not the strings are equivalent
function ix.util.StringMatches(a, b)
if (a and b) then
local a2, b2 = a:utf8lower(), b:utf8lower()
-- Check if the actual letters match.
if (a == b) then return true end
if (a2 == b2) then return true end
-- Be less strict and search.
if (a:find(b)) then return true end
if (a2:find(b2)) then return true end
end
return false
end
--- Returns a string that has the named arguments in the format string replaced with the given arguments.
-- @realm shared
-- @string format Format string
-- @tparam tab|... Arguments to pass to the formatted string. If passed a table, it will use that table as the lookup table for
-- the named arguments. If passed multiple arguments, it will replace the arguments in the string in order.
-- @usage print(ix.util.FormatStringNamed("Hi, my name is {name}.", {name = "Bobby"}))
-- > Hi, my name is Bobby.
-- @usage print(ix.util.FormatStringNamed("Hi, my name is {name}.", "Bobby"))
-- > Hi, my name is Bobby.
function ix.util.FormatStringNamed(format, ...)
local arguments = {...}
local bArray = false -- Whether or not the input has numerical indices or named ones
local input
-- If the first argument is a table, we can assumed it's going to specify which
-- keys to fill out. Otherwise we'll fill in specified arguments in order.
if (istable(arguments[1])) then
input = arguments[1]
else
input = arguments
bArray = true
end
local i = 0
local result = format:gsub("{(%w-)}", function(word)
i = i + 1
return tostring((bArray and input[i] or input[word]) or word)
end)
return result
end
do
local upperMap = {
["ooc"] = true,
["looc"] = true,
["afk"] = true,
["url"] = true
}
--- Returns a string that is the given input with spaces in between each CamelCase word. This function will ignore any words
-- that do not begin with a capital letter. The words `ooc`, `looc`, `afk`, and `url` will be automatically transformed
-- into uppercase text. This will not capitalize non-ASCII letters due to limitations with Lua's pattern matching.
-- @realm shared
-- @string input String to expand
-- @bool[opt=false] bNoUpperFirst Whether or not to avoid capitalizing the first character. This is useful for lowerCamelCase
-- @treturn string Expanded CamelCase string
-- @usage print(ix.util.ExpandCamelCase("HelloWorld"))
-- > Hello World
function ix.util.ExpandCamelCase(input, bNoUpperFirst)
input = bNoUpperFirst and input or input:utf8sub(1, 1):utf8upper() .. input:utf8sub(2)
-- extra parentheses to select first return value of gsub
return string.TrimRight((input:gsub("%u%l+", function(word)
if (upperMap[word:utf8lower()]) then
word = word:utf8upper()
end
return word .. " "
end)))
end
end
function ix.util.GridVector(vec, gridSize)
if (gridSize <= 0) then
gridSize = 1
end
for i = 1, 3 do
vec[i] = vec[i] / gridSize
vec[i] = math.Round(vec[i])
vec[i] = vec[i] * gridSize
end
return vec
end
do
local i
local value
local character
local function iterator(table)
repeat
i = i + 1
value = table[i]
character = value and value:GetCharacter()
until character or value == nil
return value, character
end
--- Returns an iterator for characters. The resulting key/values will be a player and their corresponding characters. This
-- iterator skips over any players that do not have a valid character loaded.
-- @realm shared
-- @treturn Iterator
-- @usage for client, character in ix.util.GetCharacters() do
-- print(client, character)
-- end
-- > Player [1][Bot01] character[1]
-- > Player [2][Bot02] character[2]
-- -- etc.
function ix.util.GetCharacters()
i = 0
return iterator, player.GetAll()
end
end
if (CLIENT) then
local blur = ix.util.GetMaterial("pp/blurscreen")
local surface = surface
--- Blurs the content underneath the given panel. This will fall back to a simple darkened rectangle if the player has
-- blurring disabled.
-- @realm client
-- @tparam panel panel Panel to draw the blur for
-- @number[opt=5] amount Intensity of the blur. This should be kept between 0 and 10 for performance reasons
-- @number[opt=0.2] passes Quality of the blur. This should be kept as default
-- @number[opt=255] alpha Opacity of the blur
-- @usage function PANEL:Paint(width, height)
-- ix.util.DrawBlur(self)
-- end
function ix.util.DrawBlur(panel, amount, passes, alpha)
amount = amount or 5
if (ix.option.Get("cheapBlur", false)) then
surface.SetDrawColor(50, 50, 50, alpha or (amount * 20))
surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall())
else
surface.SetMaterial(blur)
surface.SetDrawColor(255, 255, 255, alpha or 255)
local x, y = panel:LocalToScreen(0, 0)
for i = -(passes or 0.2), 1, 0.2 do
-- Do things to the blur material to make it blurry.
blur:SetFloat("$blur", i * amount)
blur:Recompute()
-- Draw the blur material over the screen.
render.UpdateScreenEffectTexture()
surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH())
end
end
end
--- Draws a blurred rectangle with the given position and bounds. This shouldn't be used for panels, see `ix.util.DrawBlur`
-- instead.
-- @realm client
-- @number x X-position of the rectangle
-- @number y Y-position of the rectangle
-- @number width Width of the rectangle
-- @number height Height of the rectangle
-- @number[opt=5] amount Intensity of the blur. This should be kept between 0 and 10 for performance reasons
-- @number[opt=0.2] passes Quality of the blur. This should be kept as default
-- @number[opt=255] alpha Opacity of the blur
-- @usage hook.Add("HUDPaint", "MyHUDPaint", function()
-- ix.util.DrawBlurAt(0, 0, ScrW(), ScrH())
-- end)
function ix.util.DrawBlurAt(x, y, width, height, amount, passes, alpha)
amount = amount or 5
if (ix.option.Get("cheapBlur", false)) then
surface.SetDrawColor(30, 30, 30, amount * 20)
surface.DrawRect(x, y, width, height)
else
surface.SetMaterial(blur)
surface.SetDrawColor(255, 255, 255, alpha or 255)
local scrW, scrH = ScrW(), ScrH()
local x2, y2 = x / scrW, y / scrH
local w2, h2 = (x + width) / scrW, (y + height) / scrH
for i = -(passes or 0.2), 1, 0.2 do
blur:SetFloat("$blur", i * amount)
blur:Recompute()
render.UpdateScreenEffectTexture()
surface.DrawTexturedRectUV(x, y, width, height, x2, y2, w2, h2)
end
end
end
--- Pushes a 3D2D blur to be rendered in the world. The draw function will be called next frame in the
-- `PostDrawOpaqueRenderables` hook.
-- @realm client
-- @func drawFunc Function to call when it needs to be drawn
function ix.util.PushBlur(drawFunc)
ix.blurRenderQueue[#ix.blurRenderQueue + 1] = drawFunc
end
--- Draws some text with a shadow.
-- @realm client
-- @string text Text to draw
-- @number x X-position of the text
-- @number y Y-position of the text
-- @color color Color of the text to draw
-- @number[opt=TEXT_ALIGN_LEFT] alignX Horizontal alignment of the text, using one of the `TEXT_ALIGN_*` constants
-- @number[opt=TEXT_ALIGN_LEFT] alignY Vertical alignment of the text, using one of the `TEXT_ALIGN_*` constants
-- @string[opt="ixGenericFont"] font Font to use for the text
-- @number[opt=color.a * 0.575] alpha Alpha of the shadow
function ix.util.DrawText(text, x, y, color, alignX, alignY, font, alpha)
color = color or color_white
return draw.TextShadow({
text = text,
font = font or "ixGenericFont",
pos = {x, y},
color = color,
xalign = alignX or TEXT_ALIGN_LEFT,
yalign = alignY or TEXT_ALIGN_LEFT
}, 1, alpha or (color.a * 0.575))
end
--- Wraps text so it does not pass a certain width. This function will try and break lines between words if it can,
-- otherwise it will break a word if it's too long.
-- @realm client
-- @string text Text to wrap
-- @number maxWidth Maximum allowed width in pixels
-- @string[opt="ixChatFont"] font Font to use for the text
function ix.util.WrapText(text, maxWidth, font)
font = font or "ixChatFont"
surface.SetFont(font)
local words = string.Explode("%s", text, true)
local lines = {}
local line = ""
local lineWidth = 0 -- luacheck: ignore 231
-- we don't need to calculate wrapping if we're under the max width
if (surface.GetTextSize(text) <= maxWidth) then
return {text}
end
for i = 1, #words do
local word = words[i]
local wordWidth = surface.GetTextSize(word)
-- this word is very long so we have to split it by character
if (wordWidth > maxWidth) then
local newWidth
for i2 = 1, word:utf8len() do
local character = word[i2]
newWidth = surface.GetTextSize(line .. character)
-- if current line + next character is too wide, we'll shove the next character onto the next line
if (newWidth > maxWidth) then
lines[#lines + 1] = line
line = ""
end
line = line .. character
end
lineWidth = newWidth
continue
end
local space = (i == 1) and "" or " "
local newLine = line .. space .. word
local newWidth = surface.GetTextSize(newLine)
if (newWidth > maxWidth) then
-- adding this word will bring us over the max width
lines[#lines + 1] = line
line = word
lineWidth = wordWidth
else
-- otherwise we tack on the new word and continue
line = newLine
lineWidth = newWidth
end
end
if (line != "") then
lines[#lines + 1] = line
end
return lines
end
local cos, sin, abs, rad1, log, pow = math.cos, math.sin, math.abs, math.rad, math.log, math.pow
-- arc drawing functions
-- by bobbleheadbob
-- https://facepunch.com/showthread.php?t=1558060
function ix.util.DrawArc(cx, cy, radius, thickness, startang, endang, roughness, color)
surface.SetDrawColor(color)
ix.util.DrawPrecachedArc(ix.util.PrecacheArc(cx, cy, radius, thickness, startang, endang, roughness))
end
function ix.util.DrawPrecachedArc(arc) -- Draw a premade arc.
for _, v in ipairs(arc) do
surface.DrawPoly(v)
end
end
function ix.util.PrecacheArc(cx, cy, radius, thickness, startang, endang, roughness)
local quadarc = {}
-- Correct start/end ang
startang = startang or 0
endang = endang or 0
-- Define step
-- roughness = roughness or 1
local diff = abs(startang - endang)
local smoothness = log(diff, 2) / 2
local step = diff / (pow(2, smoothness))
if startang > endang then
step = abs(step) * -1
end
-- Create the inner circle's points.
local inner = {}
local outer = {}
local ct = 1
local r = radius - thickness
for deg = startang, endang, step do
local rad = rad1(deg)
local cosrad, sinrad = cos(rad), sin(rad) --calculate sin, cos
local ox, oy = cx + (cosrad * r), cy + (-sinrad * r) --apply to inner distance
inner[ct] = {
x = ox,
y = oy,
u = (ox - cx) / radius + .5,
v = (oy - cy) / radius + .5
}
local ox2, oy2 = cx + (cosrad * radius), cy + (-sinrad * radius) --apply to outer distance
outer[ct] = {
x = ox2,
y = oy2,
u = (ox2 - cx) / radius + .5,
v = (oy2 - cy) / radius + .5
}
ct = ct + 1
end
-- QUAD the points.
for tri = 1, ct do
local p1, p2, p3, p4
local t = tri + 1
p1 = outer[tri]
p2 = outer[t]
p3 = inner[t]
p4 = inner[tri]
quadarc[tri] = {p1, p2, p3, p4}
end
-- Return a table of triangles to draw.
return quadarc
end
--- Resets all stencil values to known good (i.e defaults)
-- @realm client
function ix.util.ResetStencilValues()
render.SetStencilWriteMask(0xFF)
render.SetStencilTestMask(0xFF)
render.SetStencilReferenceValue(0)
render.SetStencilCompareFunction(STENCIL_ALWAYS)
render.SetStencilPassOperation(STENCIL_KEEP)
render.SetStencilFailOperation(STENCIL_KEEP)
render.SetStencilZFailOperation(STENCIL_KEEP)
render.ClearStencil()
end
-- luacheck: globals derma
-- Alternative to SkinHook that allows you to pass more arguments to skin methods
function derma.SkinFunc(name, panel, a, b, c, d, e, f, g)
local skin = (ispanel(panel) and IsValid(panel)) and panel:GetSkin() or derma.GetDefaultSkin()
if (!skin) then
return
end
local func = skin[name]
if (!func) then
return
end
return func(skin, panel, a, b, c, d, e, f, g)
end
-- Alternative to Color that retrieves from the SKIN.Colours table
function derma.GetColor(name, panel, default)
default = default or ix.config.Get("color")
local skin = panel:GetSkin()
if (!skin) then
return default
end
return skin.Colours[name] or default
end
hook.Add("OnScreenSizeChanged", "ix.OnScreenSizeChanged", function(oldWidth, oldHeight)
hook.Run("ScreenResolutionChanged", oldWidth, oldHeight)
end)
end
-- Vector extension, courtesy of code_gs
do
local VECTOR = FindMetaTable("Vector")
local CrossProduct = VECTOR.Cross
local right = Vector(0, -1, 0)
function VECTOR:Right(vUp)
if (self[1] == 0 and self[2] == 0) then
return right
end
if (vUp == nil) then
vUp = vector_up
end
local vRet = CrossProduct(self, vUp)
vRet:Normalize()
return vRet
end
function VECTOR:Up(vUp)
if (self[1] == 0 and self[2] == 0) then return Vector(-self[3], 0, 0) end
if (vUp == nil) then
vUp = vector_up
end
local vRet = CrossProduct(self, vUp)
vRet = CrossProduct(vRet, self)
vRet:Normalize()
return vRet
end
end
-- luacheck: globals FCAP_IMPULSE_USE FCAP_CONTINUOUS_USE FCAP_ONOFF_USE
-- luacheck: globals FCAP_DIRECTIONAL_USE FCAP_USE_ONGROUND FCAP_USE_IN_RADIUS
FCAP_IMPULSE_USE = 0x00000010
FCAP_CONTINUOUS_USE = 0x00000020
FCAP_ONOFF_USE = 0x00000040
FCAP_DIRECTIONAL_USE = 0x00000080
FCAP_USE_ONGROUND = 0x00000100
FCAP_USE_IN_RADIUS = 0x00000200
function ix.util.IsUseableEntity(entity, requiredCaps)
if (IsValid(entity)) then
local caps = entity:ObjectCaps()
if (bit.band(caps, bit.bor(FCAP_IMPULSE_USE, FCAP_CONTINUOUS_USE, FCAP_ONOFF_USE, FCAP_DIRECTIONAL_USE))) then
if (bit.band(caps, requiredCaps) == requiredCaps) then
return true
end
end
end
end
do
local function IntervalDistance(x, x0, x1)
-- swap so x0 < x1
if (x0 > x1) then
local tmp = x0
x0 = x1
x1 = tmp
end
if (x < x0) then
return x0-x
elseif (x > x1) then
return x - x1
end
return 0
end
local NUM_TANGENTS = 8
local tangents = {0, 1, 0.57735026919, 0.3639702342, 0.267949192431, 0.1763269807, -0.1763269807, -0.267949192431}
local traceMin = Vector(-16, -16, -16)
local traceMax = Vector(16, 16, 16)
function ix.util.FindUseEntity(player, origin, forward)
local tr
local up = forward:Up()
-- Search for objects in a sphere (tests for entities that are not solid, yet still useable)
local searchCenter = origin
-- NOTE: Some debris objects are useable too, so hit those as well
-- A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too.
local useableContents = bit.bor(MASK_SOLID, CONTENTS_DEBRIS, CONTENTS_PLAYERCLIP)
-- UNDONE: Might be faster to just fold this range into the sphere query
local pObject
local nearestDist = 1e37
-- try the hit entity if there is one, or the ground entity if there isn't.
local pNearest = NULL
for i = 1, NUM_TANGENTS do
if (i == 0) then
tr = util.TraceLine({
start = searchCenter,
endpos = searchCenter + forward * 1024,
mask = useableContents,
filter = player
})
tr.EndPos = searchCenter + forward * 1024
else
local down = forward - tangents[i] * up
down:Normalize()
tr = util.TraceHull({
start = searchCenter,
endpos = searchCenter + down * 72,
mins = traceMin,
maxs = traceMax,
mask = useableContents,
filter = player
})
tr.EndPos = searchCenter + down * 72
end
pObject = tr.Entity
local bUsable = ix.util.IsUseableEntity(pObject, 0)
while (IsValid(pObject) and !bUsable and pObject:GetMoveParent()) do
pObject = pObject:GetMoveParent()
bUsable = ix.util.IsUseableEntity(pObject, 0)
end
if (bUsable) then
local delta = tr.EndPos - tr.StartPos
local centerZ = origin.z - player:WorldSpaceCenter().z
delta.z = IntervalDistance(tr.EndPos.z, centerZ - player:OBBMins().z, centerZ + player:OBBMaxs().z)
local dist = delta:Length()
if (dist < 80) then
pNearest = pObject
-- if this is directly under the cursor just return it now
if (i == 0) then
return pObject
end
end
end
end
-- check ground entity first
-- if you've got a useable ground entity, then shrink the cone of this search to 45 degrees
-- otherwise, search out in a 90 degree cone (hemisphere)
if (IsValid(player:GetGroundEntity()) and ix.util.IsUseableEntity(player:GetGroundEntity(), FCAP_USE_ONGROUND)) then
pNearest = player:GetGroundEntity()
end
if (IsValid(pNearest)) then
-- estimate nearest object by distance from the view vector
local point = pNearest:NearestPoint(searchCenter)
nearestDist = util.DistanceToLine(searchCenter, forward, point)
end
for _, v in ipairs(ents.FindInSphere(searchCenter, 80)) do
if (!ix.util.IsUseableEntity(v, FCAP_USE_IN_RADIUS)) then
continue
end
-- see if it's more roughly in front of the player than previous guess
local point = v:NearestPoint(searchCenter)
local dir = point - searchCenter
dir:Normalize()
local dot = dir:Dot(forward)
-- Need to be looking at the object more or less
if (dot < 0.8) then
continue
end
local dist = util.DistanceToLine(searchCenter, forward, point)
if (dist < nearestDist) then
-- Since this has purely been a radius search to this point, we now
-- make sure the object isn't behind glass or a grate.
local trCheckOccluded = {}
util.TraceLine({
start = searchCenter,
endpos = point,
mask = useableContents,
filter = player,
output = trCheckOccluded
})
if (trCheckOccluded.fraction == 1.0 or trCheckOccluded.Entity == v) then
pNearest = v
nearestDist = dist
end
end
end
return pNearest
end
end
ALWAYS_RAISED = {}
ALWAYS_RAISED["weapon_physgun"] = true
ALWAYS_RAISED["gmod_tool"] = true
ALWAYS_RAISED["ix_poshelper"] = true
function ix.util.FindEmptySpace(entity, filter, spacing, size, height, tolerance)
spacing = spacing or 32
size = size or 3
height = height or 36
tolerance = tolerance or 5
local position = entity:GetPos()
local mins, maxs = Vector(-spacing * 0.5, -spacing * 0.5, 0), Vector(spacing * 0.5, spacing * 0.5, height)
local output = {}
for x = -size, size do
for y = -size, size do
local origin = position + Vector(x * spacing, y * spacing, 0)
local data = {}
data.start = origin + mins + Vector(0, 0, tolerance)
data.endpos = origin + maxs
data.filter = filter or entity
local trace = util.TraceLine(data)
data.start = origin + Vector(-maxs.x, -maxs.y, tolerance)
data.endpos = origin + Vector(mins.x, mins.y, height)
local trace2 = util.TraceLine(data)
if (trace.StartSolid or trace.Hit or trace2.StartSolid or trace2.Hit or !util.IsInWorld(origin)) then
continue
end
output[#output + 1] = origin
end
end
table.sort(output, function(a, b)
return a:DistToSqr(position) < b:DistToSqr(position)
end)
return output
end
-- Time related stuff.
do
--- Gets the current time in the UTC time-zone.
-- @realm shared
-- @treturn number Current time in UTC
function ix.util.GetUTCTime()
local date = os.date("!*t")
local localDate = os.date("*t")
localDate.isdst = false
return os.difftime(os.time(date), os.time(localDate))
end
-- Setup for time strings.
local TIME_UNITS = {}
TIME_UNITS["s"] = 1 -- Seconds
TIME_UNITS["m"] = 60 -- Minutes
TIME_UNITS["h"] = 3600 -- Hours
TIME_UNITS["d"] = TIME_UNITS["h"] * 24 -- Days
TIME_UNITS["w"] = TIME_UNITS["d"] * 7 -- Weeks
TIME_UNITS["mo"] = TIME_UNITS["d"] * 30 -- Months
TIME_UNITS["y"] = TIME_UNITS["d"] * 365 -- Years
--- Gets the amount of seconds from a given formatted string. If no time units are specified, it is assumed minutes.
-- The valid values are as follows:
--
-- - `s` - Seconds
-- - `m` - Minutes
-- - `h` - Hours
-- - `d` - Days
-- - `w` - Weeks
-- - `mo` - Months
-- - `y` - Years
-- @realm shared
-- @string text Text to interpret a length of time from
-- @treturn[1] number Amount of seconds from the length interpreted from the given string
-- @treturn[2] 0 If the given string does not have a valid time
-- @usage print(ix.util.GetStringTime("5y2d7w"))
-- > 162086400 -- 5 years, 2 days, 7 weeks
function ix.util.GetStringTime(text)
local minutes = tonumber(text)
if (minutes) then
return math.abs(minutes * 60)
end
local time = 0
for amount, unit in text:lower():gmatch("(%d+)(%a+)") do
amount = tonumber(amount)
if (amount and TIME_UNITS[unit]) then
time = time + math.abs(amount * TIME_UNITS[unit])
end
end
return time
end
end
--[[
Credit to TFA for figuring this mess out.
Original: https://steamcommunity.com/sharedfiles/filedetails/?id=903541818
]]
if (system.IsLinux()) then
local cache = {}
-- Helper Functions
local function GetSoundPath(path, gamedir)
if (!gamedir) then
path = "sound/" .. path
gamedir = "GAME"
end
return path, gamedir
end
local function f_IsWAV(f)
f:Seek(8)
return f:Read(4) == "WAVE"
end
-- WAV functions
local function f_SampleDepth(f)
f:Seek(34)
local bytes = {}
for i = 1, 2 do
bytes[i] = f:ReadByte(1)
end
local num = bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0)
return num
end
local function f_SampleRate(f)
f:Seek(24)
local bytes = {}
for i = 1, 4 do
bytes[i] = f:ReadByte(1)
end
local num = bit.lshift(bytes[4], 24) + bit.lshift(bytes[3], 16) + bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0)
return num
end
local function f_Channels(f)
f:Seek(22)
local bytes = {}
for i = 1, 2 do
bytes[i] = f:ReadByte(1)
end
local num = bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0)
return num
end
local function f_Duration(f)
return (f:Size() - 44) / (f_SampleDepth(f) / 8 * f_SampleRate(f) * f_Channels(f))
end
ixSoundDuration = ixSoundDuration or SoundDuration -- luacheck: globals ixSoundDuration
function SoundDuration(str) -- luacheck: globals SoundDuration
local path, gamedir = GetSoundPath(str)
local f = file.Open(path, "rb", gamedir)
if (!f) then return 0 end --Return nil on invalid files
local ret
if (cache[str]) then
ret = cache[str]
elseif (f_IsWAV(f)) then
ret = f_Duration(f)
else
ret = ixSoundDuration(str)
end
f:Close()
return ret
end
end
local ADJUST_SOUND = SoundDuration("npc/metropolice/pain1.wav") > 0 and "" or "../../hl2/sound/"
--- Emits sounds one after the other from an entity.
-- @realm shared
-- @entity entity Entity to play sounds from
-- @tab sounds Sound paths to play
-- @number delay[opt=0] How long to wait before starting to play the sounds
-- @number spacing[opt=0.1] How long to wait between playing each sound
-- @number volume[opt=75] The sound level of each sound
-- @number pitch[opt=100] Pitch percentage of each sound
-- @treturn number How long the entire sequence of sounds will take to play
function ix.util.EmitQueuedSounds(entity, sounds, delay, spacing, volume, pitch)
-- Let there be a delay before any sound is played.
delay = delay or 0
spacing = spacing or 0.1
-- Loop through all of the sounds.
for _, v in ipairs(sounds) do
local postSet, preSet = 0, 0
-- Determine if this sound has special time offsets.
if (istable(v)) then
postSet, preSet = v[2] or 0, v[3] or 0
v = v[1]
end
-- Get the length of the sound.
local length = SoundDuration(ADJUST_SOUND..v)
-- If the sound has a pause before it is played, add it here.
delay = delay + preSet
-- Have the sound play in the future.
timer.Simple(delay, function()
-- Check if the entity still exists and play the sound.
if (IsValid(entity)) then
entity:EmitSound(v, volume, pitch)
end
end)
-- Add the delay for the next sound.
delay = delay + length + postSet + spacing
end
-- Return how long it took for the whole thing.
return delay
end
--- Merges the contents of the second table with the content in the first one. The destination table will be modified.
--- If element is table but not metatable object, value's elements will be changed only.
-- @realm shared
-- @tab destination The table you want the source table to merge with
-- @tab source The table you want to merge with the destination table
-- @return table
function ix.util.MetatableSafeTableMerge(destination, source)
for k, v in pairs(source) do
if (istable(v) and istable(destination[k]) and getmetatable(v) == nil) then
-- don't overwrite one table with another
-- instead merge them recurisvely
ix.util.MetatableSafeTableMerge(destination[k], v);
else
destination[ k ] = v;
end
end
return destination;
end
ix.util.Include("helix/gamemode/core/meta/sh_entity.lua")
ix.util.Include("helix/gamemode/core/meta/sh_player.lua")
================================================
FILE: gamemode/init.lua
================================================
-- Include Helix content.
resource.AddWorkshop("1267236756")
-- Include features from the Sandbox gamemode.
DeriveGamemode("sandbox")
-- Define a global shared table to store Helix information.
ix = ix or {util = {}, meta = {}}
-- Send the following files to players.
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("core/sh_util.lua")
AddCSLuaFile("core/sh_data.lua")
AddCSLuaFile("shared.lua")
-- Include utility functions, data storage functions, and then shared.lua
include("core/sh_util.lua")
include("core/sh_data.lua")
include("shared.lua")
-- Resources that are required for players to download are here.
resource.AddFile("materials/helix/gui/vignette.png")
resource.AddFile("resource/fonts/fontello.ttf")
resource.AddFile("sound/helix/intro.mp3")
resource.AddFile("sound/helix/ui/press.wav")
resource.AddFile("sound/helix/ui/rollover.wav")
resource.AddFile("sound/helix/ui/whoosh1.wav")
resource.AddFile("sound/helix/ui/whoosh2.wav")
resource.AddFile("sound/helix/ui/whoosh3.wav")
resource.AddFile("sound/helix/ui/whoosh4.wav")
resource.AddFile("sound/helix/ui/whoosh5.wav")
resource.AddFile("sound/helix/ui/whoosh6.wav")
cvars.AddChangeCallback("sbox_persist", function(name, old, new)
-- A timer in case someone tries to rapily change the convar, such as addons with "live typing" or whatever
timer.Create("sbox_persist_change_timer", 1, 1, function()
hook.Run("PersistenceSave", old)
if (new == "") then
return
end
hook.Run("PersistenceLoad", new)
end)
end, "sbox_persist_load")
================================================
FILE: gamemode/items/ammo/sh_357ammo.txt
================================================
ITEM.name = ".357 Ammo"
ITEM.model = "models/items/357ammo.mdl"
ITEM.ammo = "357" -- type of the ammo
ITEM.ammoAmount = 12 -- amount of the ammo
ITEM.description = "A Box that contains %s of .357 Ammo"
ITEM.price = 10
================================================
FILE: gamemode/items/ammo/sh_ar2ammo.txt
================================================
ITEM.name = "AR2 Cartridge"
ITEM.model = "models/Items/combine_rifle_cartridge01.mdl"
ITEM.ammo = "ar2" -- type of the ammo
ITEM.ammoAmount = 30 -- amount of the ammo
ITEM.description = "A Cartridge that contains %s of AR2 Ammo"
================================================
FILE: gamemode/items/ammo/sh_crossbowammo.txt
================================================
ITEM.name = "Crossbow Bolts"
ITEM.model = "models/Items/CrossbowRounds.mdl"
ITEM.ammo = "XBowRounds" -- type of the ammo
ITEM.ammoAmount = 5 -- amount of the ammo
ITEM.description = "A Bundle of %s Crossbow Bolts"
================================================
FILE: gamemode/items/ammo/sh_pistolammo.txt
================================================
ITEM.name = "Pistol Ammo"
ITEM.model = "models/items/357ammo.mdl"
ITEM.ammo = "pistol" -- type of the ammo
ITEM.ammoAmount = 30 -- amount of the ammo
ITEM.description = "A Box that contains %s of Pistol Ammo"
================================================
FILE: gamemode/items/ammo/sh_rocketammo.txt
================================================
ITEM.name = "A Rocket"
ITEM.model = "models/weapons/w_missile_closed.mdl"
ITEM.ammo = "rpg_round" -- type of the ammo
ITEM.ammoAmount = 1 -- amount of the ammo
ITEM.width = 2
ITEM.description = "A Package of %s Rockets"
ITEM.iconCam = {
ang = Angle(-0.70499622821808, 268.25439453125, 0),
fov = 12.085652091515,
pos = Vector(7, 200, -2)
}
================================================
FILE: gamemode/items/ammo/sh_shotgunammo.txt
================================================
ITEM.name = "Shotgun Shells"
ITEM.model = "models/Items/BoxBuckshot.mdl"
ITEM.ammo = "buckshot" -- type of the ammo
ITEM.ammoAmount = 15 -- amount of the ammo
ITEM.description = "A Box of %s Shotgun Shells"
================================================
FILE: gamemode/items/ammo/sh_smg1ammo.txt
================================================
ITEM.name = "Sub Machine Gun Ammo"
ITEM.model = "models/Items/BoxSRounds.mdl"
ITEM.ammo = "smg1" -- type of the ammo
ITEM.ammoAmount = 45 -- amount of the ammo
ITEM.description = "A Box that contains %s of SMG Ammo"
================================================
FILE: gamemode/items/bags/sh_large.txt
================================================
ITEM.name = "Big Bag"
ITEM.description = "A big bag."
ITEM.invWidth = 6
ITEM.invHeight = 4
================================================
FILE: gamemode/items/bags/sh_small.txt
================================================
ITEM.name = "Small Bag"
ITEM.description = "A small bag."
================================================
FILE: gamemode/items/base/sh_ammo.lua
================================================
ITEM.name = "Ammo Base"
ITEM.model = "models/Items/BoxSRounds.mdl"
ITEM.width = 1
ITEM.height = 1
ITEM.ammo = "pistol" -- type of the ammo
ITEM.ammoAmount = 30 -- amount of the ammo
ITEM.description = "A Box that contains %s of Pistol Ammo"
ITEM.category = "Ammunition"
ITEM.useSound = "items/ammo_pickup.wav"
function ITEM:GetDescription()
local rounds = self:GetData("rounds", self.ammoAmount)
return Format(self.description, rounds)
end
if (CLIENT) then
function ITEM:PaintOver(item, w, h)
draw.SimpleText(
item:GetData("rounds", item.ammoAmount), "DermaDefault", w - 5, h - 5,
color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, 1, color_black
)
end
end
-- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item.
ITEM.functions.use = {
name = "Load",
tip = "useTip",
icon = "icon16/add.png",
OnRun = function(item)
local rounds = item:GetData("rounds", item.ammoAmount)
item.player:GiveAmmo(rounds, item.ammo)
item.player:EmitSound(item.useSound, 110)
return true
end,
}
-- Called after the item is registered into the item tables.
function ITEM:OnRegistered()
if (ix.ammo) then
ix.ammo.Register(self.ammo)
end
end
================================================
FILE: gamemode/items/base/sh_bags.lua
================================================
if (SERVER) then
util.AddNetworkString("ixBagDrop")
end
ITEM.name = "Bag"
ITEM.description = "A bag to hold items."
ITEM.model = "models/props_c17/suitcase001a.mdl"
ITEM.category = "Storage"
ITEM.width = 2
ITEM.height = 2
ITEM.invWidth = 4
ITEM.invHeight = 2
ITEM.isBag = true
ITEM.functions.View = {
icon = "icon16/briefcase.png",
OnClick = function(item)
local index = item:GetData("id", "")
if (index) then
local panel = ix.gui["inv"..index]
local inventory = ix.item.inventories[index]
local parent = IsValid(ix.gui.menuInventoryContainer) and ix.gui.menuInventoryContainer or ix.gui.openedStorage
if (IsValid(panel)) then
panel:Remove()
end
if (inventory and inventory.slots) then
panel = vgui.Create("ixInventory", IsValid(parent) and parent or nil)
panel:SetInventory(inventory)
panel:ShowCloseButton(true)
panel:SetTitle(item.GetName and item:GetName() or L(item.name))
if (parent != ix.gui.menuInventoryContainer) then
panel:Center()
if (parent == ix.gui.openedStorage) then
panel:MakePopup()
end
else
panel:MoveToFront()
end
ix.gui["inv"..index] = panel
else
ErrorNoHalt("[Helix] Attempt to view an uninitialized inventory '"..index.."'\n")
end
end
return false
end,
OnCanRun = function(item)
return !IsValid(item.entity) and item:GetData("id") and !IsValid(ix.gui["inv" .. item:GetData("id", "")])
end
}
ITEM.functions.combine = {
OnRun = function(item, data)
ix.item.instances[data[1]]:Transfer(item:GetData("id"), nil, nil, item.player)
return false
end,
OnCanRun = function(item, data)
local index = item:GetData("id", "")
if (index) then
local inventory = ix.item.inventories[index]
if (inventory) then
return true
end
end
return false
end
}
if (CLIENT) then
function ITEM:PaintOver(item, width, height)
local panel = ix.gui["inv" .. item:GetData("id", "")]
if (!IsValid(panel)) then
return
end
if (vgui.GetHoveredPanel() == self) then
panel:SetHighlighted(true)
else
panel:SetHighlighted(false)
end
end
end
-- Called when a new instance of this item has been made.
function ITEM:OnInstanced(invID, x, y)
local inventory = ix.item.inventories[invID]
ix.inventory.New(inventory and inventory.owner or 0, self.uniqueID, function(inv)
local client = inv:GetOwner()
inv.vars.isBag = self.uniqueID
self:SetData("id", inv:GetID())
if (IsValid(client)) then
inv:AddReceiver(client)
end
end)
end
function ITEM:GetInventory()
local index = self:GetData("id")
if (index) then
return ix.item.inventories[index]
end
end
ITEM.GetInv = ITEM.GetInventory
-- Called when the item first appears for a client.
function ITEM:OnSendData()
local index = self:GetData("id")
if (index) then
local inventory = ix.item.inventories[index]
if (inventory) then
inventory.vars.isBag = self.uniqueID
inventory:Sync(self.player)
inventory:AddReceiver(self.player)
else
local owner = self.player:GetCharacter():GetID()
ix.inventory.Restore(self:GetData("id"), self.invWidth, self.invHeight, function(inv)
inv.vars.isBag = self.uniqueID
inv:SetOwner(owner, true)
if (!inv.owner) then
return
end
for client, character in ix.util.GetCharacters() do
if (character:GetID() == inv.owner) then
inv:AddReceiver(client)
break
end
end
end)
end
else
ix.inventory.New(self.player:GetCharacter():GetID(), self.uniqueID, function(inv)
self:SetData("id", inv:GetID())
end)
end
end
ITEM.postHooks.drop = function(item, result)
local index = item:GetData("id")
local query = mysql:Update("ix_inventories")
query:Update("character_id", 0)
query:Where("inventory_id", index)
query:Execute()
net.Start("ixBagDrop")
net.WriteUInt(index, 32)
net.Send(item.player)
end
if (CLIENT) then
net.Receive("ixBagDrop", function()
local index = net.ReadUInt(32)
local panel = ix.gui["inv"..index]
if (panel and panel:IsVisible()) then
panel:Close()
end
end)
end
-- Called before the item is permanently deleted.
function ITEM:OnRemoved()
local index = self:GetData("id")
if (index) then
local query = mysql:Delete("ix_items")
query:Where("inventory_id", index)
query:Execute()
query = mysql:Delete("ix_inventories")
query:Where("inventory_id", index)
query:Execute()
end
end
-- Called when the item should tell whether or not it can be transfered between inventories.
function ITEM:CanTransfer(oldInventory, newInventory)
local index = self:GetData("id")
if (newInventory) then
if (newInventory.vars and newInventory.vars.isBag) then
return false
end
local index2 = newInventory:GetID()
if (index == index2) then
return false
end
for k, _ in self:GetInventory():Iter() do
if (k:GetData("id") == index2) then
return false
end
end
end
return !newInventory or newInventory:GetID() != oldInventory:GetID() or newInventory.vars.isBag
end
function ITEM:OnTransferred(curInv, inventory)
local bagInventory = self:GetInventory()
if (isfunction(curInv.GetOwner)) then
local owner = curInv:GetOwner()
if (IsValid(owner)) then
bagInventory:RemoveReceiver(owner)
end
end
if (isfunction(inventory.GetOwner)) then
local owner = inventory:GetOwner()
if (IsValid(owner)) then
bagInventory:AddReceiver(owner)
bagInventory:SetOwner(owner)
end
else
-- it's not in a valid inventory so nobody owns this bag
bagInventory:SetOwner(nil)
end
end
-- Called after the item is registered into the item tables.
function ITEM:OnRegistered()
ix.inventory.Register(self.uniqueID, self.invWidth, self.invHeight, true)
end
================================================
FILE: gamemode/items/base/sh_outfit.lua
================================================
ITEM.name = "Outfit"
ITEM.description = "A Outfit Base."
ITEM.category = "Outfit"
ITEM.model = "models/Gibs/HGIBS.mdl"
ITEM.width = 1
ITEM.height = 1
ITEM.outfitCategory = "model"
ITEM.pacData = {}
--[[
-- This will change a player's skin after changing the model. Keep in mind it starts at 0.
ITEM.newSkin = 1
-- This will change a certain part of the model.
ITEM.replacements = {"group01", "group02"}
-- This will change the player's model completely.
ITEM.replacements = "models/manhack.mdl"
-- This will have multiple replacements.
ITEM.replacements = {
{"male", "female"},
{"group01", "group02"}
}
-- This will apply body groups.
ITEM.bodyGroups = {
["blade"] = 1,
["bladeblur"] = 1
}
]]--
-- Inventory drawing
if (CLIENT) then
function ITEM:PaintOver(item, w, h)
if (item:GetData("equip")) then
surface.SetDrawColor(110, 255, 110, 100)
surface.DrawRect(w - 14, h - 14, 8, 8)
end
end
end
function ITEM:AddOutfit(client)
local character = client:GetCharacter()
self:SetData("equip", true)
local groups = character:GetData("groups", {})
-- remove original bodygroups
if (!table.IsEmpty(groups)) then
character:SetData("oldGroups" .. self.outfitCategory, groups)
character:SetData("groups", {})
client:ResetBodygroups()
end
if (isfunction(self.OnGetReplacement)) then
character:SetData("oldModel" .. self.outfitCategory,
character:GetData("oldModel" .. self.outfitCategory, self.player:GetModel()))
character:SetModel(self:OnGetReplacement())
elseif (self.replacement or self.replacements) then
character:SetData("oldModel" .. self.outfitCategory,
character:GetData("oldModel" .. self.outfitCategory, self.player:GetModel()))
if (istable(self.replacements)) then
if (#self.replacements == 2 and isstring(self.replacements[1])) then
character:SetModel(self.player:GetModel():gsub(self.replacements[1], self.replacements[2]))
else
for _, v in ipairs(self.replacements) do
character:SetModel(self.player:GetModel():gsub(v[1], v[2]))
end
end
else
character:SetModel(self.replacement or self.replacements)
end
end
if (self.newSkin) then
character:SetData("oldSkin" .. self.outfitCategory, self.player:GetSkin())
self.player:SetSkin(self.newSkin)
end
-- get outfit saved bodygroups
groups = self:GetData("groups", {})
-- restore bodygroups saved to the item
if (!table.IsEmpty(groups) and self:ShouldRestoreBodygroups()) then
for k, v in pairs(groups) do
client:SetBodygroup(k, v)
end
-- apply default item bodygroups if none are saved
elseif (istable(self.bodyGroups)) then
for k, v in pairs(self.bodyGroups) do
local index = client:FindBodygroupByName(k)
if (index > -1) then
client:SetBodygroup(index, v)
end
end
end
local materials = self:GetData("submaterial", {})
if (!table.IsEmpty(materials) and self:ShouldRestoreSubMaterials()) then
for k, v in pairs(materials) do
if (!isnumber(k) or !isstring(v)) then
continue
end
client:SetSubMaterial(k - 1, v)
end
end
if (istable(self.attribBoosts)) then
for k, v in pairs(self.attribBoosts) do
character:AddBoost(self.uniqueID, k, v)
end
end
self:GetOwner():SetupHands()
self:OnEquipped()
end
local function ResetSubMaterials(client)
for k, _ in ipairs(client:GetMaterials()) do
if (client:GetSubMaterial(k - 1) != "") then
client:SetSubMaterial(k - 1)
end
end
end
function ITEM:RemoveOutfit(client)
local character = client:GetCharacter()
self:SetData("equip", false)
local materials = {}
for k, _ in ipairs(client:GetMaterials()) do
if (client:GetSubMaterial(k - 1) != "") then
materials[k] = client:GetSubMaterial(k - 1)
end
end
-- save outfit submaterials
if (!table.IsEmpty(materials)) then
self:SetData("submaterial", materials)
end
-- remove outfit submaterials
ResetSubMaterials(client)
local groups = {}
for i = 0, (client:GetNumBodyGroups() - 1) do
local bodygroup = client:GetBodygroup(i)
if (bodygroup > 0) then
groups[i] = bodygroup
end
end
-- save outfit bodygroups
if (!table.IsEmpty(groups)) then
self:SetData("groups", groups)
end
-- remove outfit bodygroups
client:ResetBodygroups()
-- restore the original player model
if (character:GetData("oldModel" .. self.outfitCategory)) then
character:SetModel(character:GetData("oldModel" .. self.outfitCategory))
character:SetData("oldModel" .. self.outfitCategory, nil)
end
-- restore the original player model skin
if (self.newSkin) then
if (character:GetData("oldSkin" .. self.outfitCategory)) then
client:SetSkin(character:GetData("oldSkin" .. self.outfitCategory))
character:SetData("oldSkin" .. self.outfitCategory, nil)
else
client:SetSkin(0)
end
end
-- get character original bodygroups
groups = character:GetData("oldGroups" .. self.outfitCategory, {})
-- restore original bodygroups
if (!table.IsEmpty(groups)) then
for k, v in pairs(groups) do
client:SetBodygroup(k, v)
end
character:SetData("groups", character:GetData("oldGroups" .. self.outfitCategory, {}))
character:SetData("oldGroups" .. self.outfitCategory, nil)
end
if (istable(self.attribBoosts)) then
for k, _ in pairs(self.attribBoosts) do
character:RemoveBoost(self.uniqueID, k)
end
end
for k, _ in pairs(self:GetData("outfitAttachments", {})) do
self:RemoveAttachment(k, client)
end
self:GetOwner():SetupHands()
self:OnUnequipped()
end
-- makes another outfit depend on this outfit in terms of requiring this item to be equipped in order to equip the attachment
-- also unequips the attachment if this item is dropped
function ITEM:AddAttachment(id)
local attachments = self:GetData("outfitAttachments", {})
attachments[id] = true
self:SetData("outfitAttachments", attachments)
end
function ITEM:RemoveAttachment(id, client)
local item = ix.item.instances[id]
local attachments = self:GetData("outfitAttachments", {})
if (item and attachments[id]) then
item:OnDetached(client)
end
attachments[id] = nil
self:SetData("outfitAttachments", attachments)
end
ITEM:Hook("drop", function(item)
if (item:GetData("equip")) then
local character = ix.char.loaded[item.owner]
local client = character and character:GetPlayer() or item:GetOwner()
item.player = client
item:RemoveOutfit(item:GetOwner())
end
end)
ITEM.functions.EquipUn = { -- sorry, for name order.
name = "unequip",
tip = "unequipTip",
icon = "icon16/cross.png",
OnRun = function(item)
item:RemoveOutfit(item.player)
return false
end,
OnCanRun = function(item)
local client = item.player
return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and
hook.Run("CanPlayerUnequipItem", client, item) != false
end
}
ITEM.functions.Equip = {
name = "equip",
tip = "equipTip",
icon = "icon16/tick.png",
OnRun = function(item)
local client = item.player
local char = client:GetCharacter()
for k, _ in char:GetInventory():Iter() do
if (k.id != item.id) then
local itemTable = ix.item.instances[k.id]
if (itemTable.pacData and k.outfitCategory == item.outfitCategory and itemTable:GetData("equip")) then
client:NotifyLocalized(item.equippedNotify or "outfitAlreadyEquipped")
return false
end
end
end
item:AddOutfit(item.player)
return false
end,
OnCanRun = function(item)
local client = item.player
return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and item:CanEquipOutfit() and
hook.Run("CanPlayerEquipItem", client, item) != false
end
}
function ITEM:CanTransfer(oldInventory, newInventory)
if (newInventory and self:GetData("equip")) then
return false
end
return true
end
function ITEM:OnRemoved()
if (self.invID != 0 and self:GetData("equip")) then
self.player = self:GetOwner()
self:RemoveOutfit(self.player)
self.player = nil
end
end
function ITEM:OnEquipped()
end
function ITEM:OnUnequipped()
end
function ITEM:CanEquipOutfit()
return true
end
function ITEM:ShouldRestoreBodygroups()
return true
end
function ITEM:ShouldRestoreSubMaterials()
return true
end
================================================
FILE: gamemode/items/base/sh_pacoutfit.lua
================================================
ITEM.name = "PAC Outfit"
ITEM.description = "A PAC Outfit Base."
ITEM.category = "Outfit"
ITEM.model = "models/Gibs/HGIBS.mdl"
ITEM.width = 1
ITEM.height = 1
ITEM.outfitCategory = "hat"
ITEM.pacData = {}
--[[
ITEM.pacData = {
[1] = {
["children"] = {
[1] = {
["children"] = {
},
["self"] = {
["Angles"] = Angle(12.919322967529, 6.5696062847564e-006, -1.0949343050015e-005),
["Position"] = Vector(-2.099609375, 0.019973754882813, 1.0180969238281),
["UniqueID"] = "4249811628",
["Size"] = 1.25,
["Bone"] = "eyes",
["Model"] = "models/Gibs/HGIBS.mdl",
["ClassName"] = "model",
},
},
},
["self"] = {
["ClassName"] = "group",
["UniqueID"] = "907159817",
["EditorExpand"] = true,
},
},
}
-- This will change a player's skin after changing the model. Keep in mind it starts at 0.
ITEM.newSkin = 1
-- This will change a certain part of the model.
ITEM.replacements = {"group01", "group02"}
-- This will change the player's model completely.
ITEM.replacements = "models/manhack.mdl"
-- This will have multiple replacements.
ITEM.replacements = {
{"male", "female"},
{"group01", "group02"}
}
-- This will apply body groups.
ITEM.bodyGroups = {
["blade"] = 1,
["bladeblur"] = 1
}
--]]
-- Inventory drawing
if (CLIENT) then
-- Draw camo if it is available.
function ITEM:PaintOver(item, w, h)
if (item:GetData("equip")) then
surface.SetDrawColor(110, 255, 110, 100)
surface.DrawRect(w - 14, h - 14, 8, 8)
end
end
end
function ITEM:RemovePart(client)
local char = client:GetCharacter()
self:SetData("equip", false)
client:RemovePart(self.uniqueID)
if (self.attribBoosts) then
for k, _ in pairs(self.attribBoosts) do
char:RemoveBoost(self.uniqueID, k)
end
end
self:OnUnequipped()
end
-- On item is dropped, Remove a weapon from the player and keep the ammo in the item.
ITEM:Hook("drop", function(item)
if (item:GetData("equip")) then
item:RemovePart(item:GetOwner())
end
end)
-- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item.
ITEM.functions.EquipUn = { -- sorry, for name order.
name = "unequip",
tip = "unequipTip",
icon = "icon16/cross.png",
OnRun = function(item)
item:RemovePart(item.player)
return false
end,
OnCanRun = function(item)
local client = item.player
return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and
hook.Run("CanPlayerUnequipItem", client, item) != false
end
}
-- On player eqipped the item, Gives a weapon to player and load the ammo data from the item.
ITEM.functions.Equip = {
name = "equip",
tip = "equipTip",
icon = "icon16/tick.png",
OnRun = function(item)
local char = item.player:GetCharacter()
for k, _ in char:GetInventory():Iter() do
if (k.id != item.id) then
local itemTable = ix.item.instances[k.id]
if (itemTable.pacData and k.outfitCategory == item.outfitCategory and itemTable:GetData("equip")) then
item.player:NotifyLocalized(item.equippedNotify or "outfitAlreadyEquipped")
return false
end
end
end
item:SetData("equip", true)
item.player:AddPart(item.uniqueID, item)
if (item.attribBoosts) then
for k, v in pairs(item.attribBoosts) do
char:AddBoost(item.uniqueID, k, v)
end
end
item:OnEquipped()
return false
end,
OnCanRun = function(item)
local client = item.player
return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and
hook.Run("CanPlayerEquipItem", client, item) != false
end
}
function ITEM:CanTransfer(oldInventory, newInventory)
if (newInventory and self:GetData("equip")) then
return false
end
return true
end
function ITEM:OnRemoved()
local inventory = ix.item.inventories[self.invID]
local owner = inventory.GetOwner and inventory:GetOwner()
if (IsValid(owner) and owner:IsPlayer()) then
if (self:GetData("equip")) then
self:RemovePart(owner)
end
end
end
function ITEM:OnEquipped()
end
function ITEM:OnUnequipped()
end
================================================
FILE: gamemode/items/base/sh_weapons.lua
================================================
ITEM.name = "Weapon"
ITEM.description = "A Weapon."
ITEM.category = "Weapons"
ITEM.model = "models/weapons/w_pistol.mdl"
ITEM.class = "weapon_pistol"
ITEM.width = 2
ITEM.height = 2
ITEM.isWeapon = true
ITEM.isGrenade = false
ITEM.weaponCategory = "sidearm"
ITEM.useSound = "items/ammo_pickup.wav"
-- Inventory drawing
if (CLIENT) then
function ITEM:PaintOver(item, w, h)
if (item:GetData("equip")) then
surface.SetDrawColor(110, 255, 110, 100)
surface.DrawRect(w - 14, h - 14, 8, 8)
end
end
function ITEM:PopulateTooltip(tooltip)
if (self:GetData("equip")) then
local name = tooltip:GetRow("name")
name:SetBackgroundColor(derma.GetColor("Success", tooltip))
end
end
end
-- On item is dropped, Remove a weapon from the player and keep the ammo in the item.
ITEM:Hook("drop", function(item)
local inventory = ix.item.inventories[item.invID]
if (!inventory) then
return
end
-- the item could have been dropped by someone else (i.e someone searching this player), so we find the real owner
local owner
for client, character in ix.util.GetCharacters() do
if (character:GetID() == inventory.owner) then
owner = client
break
end
end
if (!IsValid(owner)) then
return
end
if (item:GetData("equip")) then
item:SetData("equip", nil)
owner.carryWeapons = owner.carryWeapons or {}
local weapon = owner.carryWeapons[item.weaponCategory]
if (!IsValid(weapon)) then
weapon = owner:GetWeapon(item.class)
end
if (IsValid(weapon)) then
item:SetData("ammo", weapon:Clip1())
owner:StripWeapon(item.class)
owner.carryWeapons[item.weaponCategory] = nil
owner:EmitSound(item.useSound, 80)
end
item:RemovePAC(owner)
end
end)
-- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item.
ITEM.functions.EquipUn = { -- sorry, for name order.
name = "unequip",
tip = "unequipTip",
icon = "icon16/cross.png",
OnRun = function(item)
item:Unequip(item.player, true)
return false
end,
OnCanRun = function(item)
local client = item.player
return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and
hook.Run("CanPlayerUnequipItem", client, item) != false
end
}
-- On player eqipped the item, Gives a weapon to player and load the ammo data from the item.
ITEM.functions.Equip = {
name = "equip",
tip = "equipTip",
icon = "icon16/tick.png",
OnRun = function(item)
item:Equip(item.player)
return false
end,
OnCanRun = function(item)
local client = item.player
return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and
hook.Run("CanPlayerEquipItem", client, item) != false
end
}
function ITEM:WearPAC(client)
if (ix.pac and self.pacData) then
client:AddPart(self.uniqueID, self)
end
end
function ITEM:RemovePAC(client)
if (ix.pac and self.pacData) then
client:RemovePart(self.uniqueID)
end
end
function ITEM:Equip(client, bNoSelect, bNoSound)
client.carryWeapons = client.carryWeapons or {}
for k, _ in client:GetCharacter():GetInventory():Iter() do
if (k.id != self.id) then
local itemTable = ix.item.instances[k.id]
if (!itemTable) then
client:NotifyLocalized("tellAdmin", "wid!xt")
return false
else
if (itemTable.isWeapon and client.carryWeapons[self.weaponCategory] and itemTable:GetData("equip")) then
client:NotifyLocalized("weaponSlotFilled", self.weaponCategory)
return false
end
end
end
end
if (client:HasWeapon(self.class)) then
client:StripWeapon(self.class)
end
local weapon = client:Give(self.class, !self.isGrenade)
if (IsValid(weapon)) then
local ammoType = weapon:GetPrimaryAmmoType()
client.carryWeapons[self.weaponCategory] = weapon
if (!bNoSelect) then
client:SelectWeapon(weapon:GetClass())
end
if (!bNoSound) then
client:EmitSound(self.useSound, 80)
end
-- Remove default given ammo.
if (client:GetAmmoCount(ammoType) == weapon:Clip1() and self:GetData("ammo", 0) == 0) then
client:RemoveAmmo(weapon:Clip1(), ammoType)
end
-- assume that a weapon with -1 clip1 and clip2 would be a throwable (i.e hl2 grenade)
-- TODO: figure out if this interferes with any other weapons
if (weapon:GetMaxClip1() == -1 and weapon:GetMaxClip2() == -1 and client:GetAmmoCount(ammoType) == 0) then
client:SetAmmo(1, ammoType)
end
self:SetData("equip", true)
if (self.isGrenade) then
weapon:SetClip1(1)
client:SetAmmo(0, ammoType)
else
weapon:SetClip1(self:GetData("ammo", 0))
end
weapon.ixItem = self
if (self.OnEquipWeapon) then
self:OnEquipWeapon(client, weapon)
end
else
print(Format("[Helix] Cannot equip weapon - %s does not exist!", self.class))
end
end
function ITEM:Unequip(client, bPlaySound, bRemoveItem)
client.carryWeapons = client.carryWeapons or {}
local weapon = client.carryWeapons[self.weaponCategory]
if (!IsValid(weapon)) then
weapon = client:GetWeapon(self.class)
end
if (IsValid(weapon)) then
weapon.ixItem = nil
self:SetData("ammo", weapon:Clip1())
client:StripWeapon(self.class)
else
print(Format("[Helix] Cannot unequip weapon - %s does not exist!", self.class))
end
if (bPlaySound) then
client:EmitSound(self.useSound, 80)
end
client.carryWeapons[self.weaponCategory] = nil
self:SetData("equip", nil)
self:RemovePAC(client)
if (self.OnUnequipWeapon) then
self:OnUnequipWeapon(client, weapon)
end
if (bRemoveItem) then
self:Remove()
end
end
function ITEM:CanTransfer(oldInventory, newInventory)
if (newInventory and self:GetData("equip")) then
local owner = self:GetOwner()
if (IsValid(owner)) then
owner:NotifyLocalized("equippedWeapon")
end
return false
end
return true
end
function ITEM:OnLoadout()
if (self:GetData("equip")) then
local client = self.player
client.carryWeapons = client.carryWeapons or {}
local weapon = client:Give(self.class, true)
if (IsValid(weapon)) then
client:RemoveAmmo(weapon:Clip1(), weapon:GetPrimaryAmmoType())
client.carryWeapons[self.weaponCategory] = weapon
weapon.ixItem = self
weapon:SetClip1(self:GetData("ammo", 0))
if (self.OnEquipWeapon) then
self:OnEquipWeapon(client, weapon)
end
else
print(Format("[Helix] Cannot give weapon - %s does not exist!", self.class))
end
end
end
function ITEM:OnSave()
local weapon = self.player:GetWeapon(self.class)
if (IsValid(weapon) and weapon.ixItem == self and self:GetData("equip")) then
self:SetData("ammo", weapon:Clip1())
end
end
function ITEM:OnRemoved()
local inventory = ix.item.inventories[self.invID]
local owner = inventory.GetOwner and inventory:GetOwner()
if (IsValid(owner) and owner:IsPlayer()) then
local weapon = owner:GetWeapon(self.class)
if (IsValid(weapon)) then
weapon:Remove()
end
self:RemovePAC(owner)
end
end
hook.Add("PlayerDeath", "ixStripClip", function(client)
client.carryWeapons = {}
for k, _ in client:GetCharacter():GetInventory():Iter() do
if (k.isWeapon and k:GetData("equip")) then
k:SetData("ammo", nil)
k:SetData("equip", nil)
if (k.pacData) then
k:RemovePAC(client)
end
end
end
end)
hook.Add("EntityRemoved", "ixRemoveGrenade", function(entity)
-- hack to remove hl2 grenades after they've all been thrown
if (entity:GetClass() == "weapon_frag") then
local client = entity:GetOwner()
if (IsValid(client) and client:IsPlayer() and client:GetCharacter()) then
local ammoName = game.GetAmmoName(entity:GetPrimaryAmmoType())
if (isstring(ammoName) and ammoName:lower() == "grenade" and client:GetAmmoCount(ammoName) < 1
and entity.ixItem and entity.ixItem.Unequip) then
entity.ixItem:Unequip(client, false, true)
end
end
end
end)
================================================
FILE: gamemode/items/pacoutfit/sh_skullmask.txt
================================================
ITEM.name = "Skull Mask"
ITEM.description = "It's a skull mask."
ITEM.model = "models/Gibs/HGIBS.mdl"
ITEM.width = 1
ITEM.height = 1
ITEM.outfitCategory = "hat"
ITEM.pacData = {
[1] = {
["children"] = {
[1] = {
["children"] = {
},
["self"] = {
["Angles"] = Angle(12.919322967529, 6.5696062847564e-006, -1.0949343050015e-005),
["Position"] = Vector(-2.099609375, 0.019973754882813, 1.3180969238281),
["UniqueID"] = "4249811628",
["Size"] = 1.25,
["Bone"] = "eyes",
["Model"] = "models/Gibs/HGIBS.mdl",
["ClassName"] = "model",
},
},
},
["self"] = {
["ClassName"] = "group",
["UniqueID"] = "907159817",
["EditorExpand"] = true,
},
},
}
================================================
FILE: gamemode/items/sh_defaultitem.txt
================================================
ITEM.name = "Test Item"
ITEM.description = "A test item!"
ITEM.model = "models/props_c17/oildrum001.mdl"
================================================
FILE: gamemode/items/weapons/sh_357.txt
================================================
ITEM.name = "357"
ITEM.description = "A sidearm utilising .357 Caliber ammunition."
ITEM.model = "models/weapons/w_357.mdl"
ITEM.class = "weapon_357"
ITEM.weaponCategory = "sidearm"
ITEM.width = 2
ITEM.height = 1
ITEM.iconCam = {
ang = Angle(-17.581502914429, 250.7974395752, 0),
fov = 5.412494001838,
pos = Vector(57.109928131104, 181.7945098877, -60.738327026367)
}
================================================
FILE: gamemode/items/weapons/sh_ar2.txt
================================================
ITEM.name = "AR2"
ITEM.description = "A Weapon."
ITEM.model = "models/weapons/w_IRifle.mdl"
ITEM.class = "weapon_ar2"
ITEM.weaponCategory = "primary"
ITEM.width = 4
ITEM.height = 2
ITEM.iconCam = {
ang = Angle(-0.70499622821808, 268.25439453125, 0),
fov = 12.085652091515,
pos = Vector(0, 200, 0)
}
================================================
FILE: gamemode/items/weapons/sh_crowbar.txt
================================================
ITEM.name = "Crowbar"
ITEM.description = "A slightly rusty looking crowbar."
ITEM.model = "models/weapons/w_crowbar.mdl"
ITEM.class = "weapon_crowbar"
ITEM.weaponCategory = "melee"
ITEM.width = 2
ITEM.height = 1
ITEM.iconCam = {
ang = Angle(-0.23955784738064, 270.44906616211, 0),
fov = 10.780103254469,
pos = Vector(0, 200, 0)
}
================================================
FILE: gamemode/items/weapons/sh_pistol.txt
================================================
ITEM.name = "9MM Pistol"
ITEM.description = "A sidearm utilising 9mm Ammunition."
ITEM.model = "models/weapons/w_pistol.mdl"
ITEM.class = "weapon_pistol"
ITEM.weaponCategory = "sidearm"
ITEM.width = 2
ITEM.height = 1
ITEM.iconCam = {
ang = Angle(0.33879372477531, 270.15808105469, 0),
fov = 5.0470897275697,
pos = Vector(0, 200, -1)
}
================================================
FILE: gamemode/items/weapons/sh_smg1.txt
================================================
ITEM.name = "Sub Machine Gun"
ITEM.description = "A Weapon."
ITEM.model = "models/weapons/w_smg1.mdl"
ITEM.class = "weapon_smg1"
ITEM.weaponCategory = "primary"
ITEM.width = 3
ITEM.height = 2
ITEM.iconCam = {
ang = Angle(-0.020070368424058, 270.40155029297, 0),
fov = 7.2253324508038,
pos = Vector(0, 200, -1)
}
================================================
FILE: gamemode/languages/sh_dutch.lua
================================================
-- DUTCH TRANSLATION - http://steamcommunity.com/profiles/76561198084653538/
NAME = "Nederlands"
LANGUAGE = {
loading = "Laden",
dbError = "Database verbinding gefaald",
unknown = "Onbekend",
noDesc = "Geen beschrijving beschikbaar",
create = "Creëer",
createTip = "Maak een nieuw karakter aan om mee te spelen.",
load = "Laad",
loadTip = "Kies een eerder gemaakte karakter om mee te spelen.",
leave = "Verlaat",
leaveTip = "Verlaat de huidige server.",
["return"] = "Terug",
returnTip = "Ga terug naar het vorige menu.",
name = "Naam",
description = "Beschrijving",
model = "Model",
attributes = "Attributen",
charCreateTip = "Vul de velden hieronder in en druk op 'Voltooi' om je karakter te maken.",
invalid = "Je hebt een ongeldige %s voorzien",
descMinLen = "Je beschrijving moet ten minste %d tekens zijn.",
player = "Speler",
finish = "Voltooi",
finishTip = "Voltooi het maken van jouw karakter.",
needModel = "Je moet een geldig 'Model' kiezen.",
creating = "Je karakter wordt gemaakt...",
unknownError = "Er is een onbekende fout opgetreden",
delConfirm = "Weet je zeker dat je %s wilt verwijderen?",
no = "Nee",
yes = "Ja",
itemInfo = "Naam: %s\nBeschrijving: %s",
cloud_no_repo = "Deze opslaagplaats is niet geldig.",
cloud_no_plugin = "Deze plugin is niet geldig.",
inv = "Inventaris",
plugins = "Plugins",
author = "Auteur",
version = "Versie",
characters = "Karakters",
business = "Bedrijf",
settings = "Instellingen",
config = "Config",
chat = "Chat",
appearance = "Uiterlijk",
misc = "Gemengd",
oocDelay = "Je moet %s seconde(n) wachten voordat je weer in OOC kan praten.",
loocDelay = "Je moet %s seconde(n) wachten voordat je weer in LOOC kan praten.",
usingChar = "Je bent dit karakter al aan het gebruiken.",
itemNoExist = "Sorry, het item dat je hebt opgevraagd bestaat niet.",
cmdNoExist = "Sorry, dat commando bestaat niet.",
plyNoExist = "Sorry, Er is geen speler gevonden met die naam.",
cfgSet = "%s heeft \"%s\" gezet tot %s.",
drop = "Vallen",
dropTip = "Dit laat deze item(s) vallen uit je inventaris.",
take = "Nemen",
takeTip = "Dit laat deze item(s) oppakken en het in je inventaris doen.",
dTitle = "Unowned Door",
dTitleOwned = "Purchased Door",
dIsNotOwnable = "Deze deur kan niet gekocht worden.",
dIsOwnable = "Je kan deze deur kopen door op 'F2' te drukken.",
dMadeUnownable = "Je hebt deze deur niet verkoopbaar gemaakt.",
dMadeOwnable = "Je hebt deze deur verkoopbaar gemaakt.",
dNotAllowedToOwn = "Je mag deze deur niet bezitten.",
dSetDisabled = "Je hebt deze deur uitgeschakeld.",
dSetNotDisabled = "Je hebt deze deur ingeschakeld.",
dSetParentDoor = "Je hebt deze deur als je 'Parent' deur gezet. ",
dCanNotSetAsChild = "Je kan deze 'Parent' deur niet als een 'Child' deur zetten.",
dAddChildDoor = "Je hebt deze deur als een 'Child' toegevoegd.",
dRemoveChildren = "Je hebt alle 'Childs' van deze deur verwijderd.",
dRemoveChildDoor = "Je hebt de 'Child' status van deze deur verwijderd.",
dNoParentDoor = "Je hebt nog geen 'Parent' deur ingeschakeld.",
dOwnedBy = "%s is de eigenaar van deze deur.",
dConfigName = "Deuren",
dSetFaction = "Deze deur behoort nu toe aan de %s 'faction'.",
dRemoveFaction = "Deze deur behoort niet meer toe aan een 'faction'.",
dNotValid = "Je kijkt niet naar een geldige deur.",
canNotAfford = "Je kunt het je niet veroorloven om dit te kopen.",
dPurchased = "Je hebt deze deur gekocht voor %s.",
dSold = "Je hebt deze deur verkocht voor %s.",
notOwner = "Je bent niet de eigenaar van dit.",
invalidArg = "Je hebt een ongeldige waarde voor argument #%s.",
flagGive = "%s heeft %s '%s' flags gegeven.",
flagTake = "%s heeft '%s' flags van %s genomen.",
flagNoMatch = "Je moet (de) \"%s\" Flag(s) hebben om dit te doen",
textAdded = "Je hebt een tekst toegevoegd.",
textRemoved = "Je hebt de %s tekst(en) verwijderd.",
moneyTaken = "Je hebt %s gevonden.",
businessPurchase = "Je hebt %s gekocht voor %s.",
businessSell = "Je verkocht %s voor %s.",
cChangeModel = "%s veranderde %s's model naar %s.",
cChangeName = "%s veranderde %s's naam naar %s.",
playerCharBelonging = "Dit object behoort aan je andere karakter toe.",
invalidFaction = "Je hebt een ongeldige 'faction' voorzien",
spawnAdd = "Je hebt een spawn toegevoegd voor de %s 'faction'.",
spawnDeleted = "Je hebt (een) %s spawn punt(en) verwijderd.",
someone = "Iemand",
rgnLookingAt = "Toestaan dat de persoon waar je naar kijkt je herkent.",
rgnWhisper = "Toestaan dat degene(n) in het fluister gebied je herkent.",
rgnTalk = "Toestaan dat de personen in het praat gebied je herkennen.",
rgnYell = "Toestaan dat de personen in het schreeuw gebied je herkennen.",
icFormat = "%s zegt \"%s\"",
rollFormat = "%s heeft %s uitgerold.",
wFormat = "%s fluistert \"%s\"",
yFormat = "%s schreeuwt \"%s\"",
sbOptions = "Klik om opties over %s te zien.",
spawnAdded = "Je hebt een spawn toegevoegd voor %s.",
whitelist = "%s heeft %s gewhitelist voor de %s 'faction'.",
unwhitelist = "%s heeft %s geblacklist van de %s 'faction'.",
gettingUp = "Je staat nu op...",
wakingUp = "Je krijgt je bewustzijn terug...",
Weapons = "Wapens",
checkout = "Ga naar checkout (%s)",
purchase = "Koop",
purchasing = "Aan het kopen...",
success = "Succes",
buyFailed = "De koop is gefaald!",
buyGood = "Succesvol gekocht!",
shipment = "Lading",
shipmentDesc = "Deze lading behoort toe aan %s.",
class = "Class",
classes = "Classes",
illegalAccess = "Illegale toegang",
becomeClassFail = "Het is niet gelukt om %s te worden.",
becomeClass = "Je bent %s geworden.",
attributeSet = "You set %s's %s to %s.",
attributeUpdate = "You added %s's %s by %s.",
noFit = "Deze item(s) passen niet in je inventaris",
help = "Hulp",
commands = "Commando's",
helpDefault = "Selecteer een categorie",
doorSettings = "Deur Instellingen",
sell = "Verkoop",
access = "Toegang",
locking = "Bezig deze 'Entity' opslot te zetten ...",
unlocking = "Bezig deze 'Entity' van het slot te zetten ...",
modelNoSeq = "Je model ondersteunt deze 'Act' niet.",
notNow = "Je kan dat nu niet doen.",
faceWall = "Je moet de muur aankijken om dit te doen.",
faceWallBack = "Je moet met je rug tegen de muur staan om dit te doen",
descChanged = "Je hebt je karakter's beschrijving veranderd.",
charMoney = "Je hebt momenteel %s.",
charFaction = "Je bent een lid van de %s 'faction'.",
charClass = "Je bent %s van de 'faction'.",
noOwner = "De eigenaar is niet geldig.",
notAllowed = "Dit is niet toegestaan.",
invalidIndex = "De Item Index is Ongeldig.",
invalidItem = "De Item Object is Ongeldig.",
invalidInventory = "Het inventaris object is ongeldig.",
home = "Home",
charKick = "%s kickde karakter %s.",
charBan = "%s heeft karakter %s verbannen.",
charBanned = "Deze karakter is verbannen.",
setMoney = "Je hebt %s's geld tot %s gezet."
}
================================================
FILE: gamemode/languages/sh_english.lua
================================================
NAME = "English"
LANGUAGE = {
helix = "Helix",
introTextOne = "fist industries presents",
introTextTwo = "in collaboration with %s",
introContinue = "press space to continue",
helpIdle = "Select a category",
helpCommands = "Command parameters with are required, while [brackets] are optional.",
helpFlags = "Flags with a green background are accessible by this character.",
creditSpecial = "Special Thanks",
creditLeadDeveloper = "Lead Developer",
creditUIDesigner = "UI Designer",
creditManager = "Project Manager",
creditTester = "Lead Tester",
chatTyping = "Typing...",
chatTalking = "Talking...",
chatYelling = "Yelling...",
chatWhispering = "Whispering...",
chatPerforming = "Performing...",
chatNewTab = "New tab...",
chatReset = "Reset position",
chatResetTabs = "Reset tabs",
chatCustomize = "Customize...",
chatCloseTab = "Close Tab",
chatTabName = "Tab Name",
chatNewTabTitle = "New Tab",
chatAllowedClasses = "Allowed Chat Classes",
chatTabExists = "A chat tab with that name already exists!",
chatMarkRead = "Mark all as read",
community = "Community",
checkAll = "Check All",
uncheckAll = "Uncheck All",
color = "Color",
type = "Type",
display = "Display",
loading = "Loading",
dbError = "Database connection failed",
unknown = "Unknown",
noDesc = "No description available",
create = "Create",
update = "Update",
load = "Load character",
loadTitle = "Load a Character",
leave = "Leave server",
leaveTip = "Leave the current server.",
["return"] = "Return",
returnTip = "Return to the previous menu.",
proceed = "Proceed",
faction = "Faction",
skills = "Skills",
choose = "Choose",
chooseFaction = "Choose a Faction",
chooseDescription = "Define your Narrative",
chooseSkills = "Hone your Skills",
name = "Name",
description = "Description",
model = "Model",
attributes = "Attributes",
attribPointsLeft = "Points left",
charInfo = "Character Information",
charCreated = "You have successfully created your character.",
charCreateTip = "Fill in the fields below and press 'Finish' to create your character.",
invalid = "You have provided an invalid %s!",
nameMinLen = "Your name must be at least %d characters!",
nameMaxLen = "Your name must not be more than %d characters!",
descMinLen = "Your description must be at least %d characters!",
maxCharacters = "You cannot create any more characters!",
player = "Player",
finish = "Finish",
finishTip = "Finish creating the character.",
needModel = "You need to choose a valid model!",
creating = "Your character is being created...",
unknownError = "An unknown error has occured!",
areYouSure = "Are you sure?",
delete = "Delete",
deleteConfirm = "This character will be irrevocably removed!",
deleteComplete = "%s has been deleted.",
no = "No",
yes = "Yes",
close = "Close",
save = "Save",
itemInfo = "Name: %s\nDescription: %s",
itemCreated = "Item(s) successfully created.",
cloud_no_repo = "The repository provided is not valid!",
cloud_no_plugin = "The plugin provided is not valid!",
inv = "Inventory",
plugins = "Plugins",
pluginLoaded = "%s has enabled the \"%s\" plugin to load next map change.",
pluginUnloaded = "%s has disabled the \"%s\" plugin from loading next map change.",
loadedPlugins = "Loaded Plugins",
unloadedPlugins = "Unloaded Plugins",
on = "On",
off = "Off",
author = "Author",
version = "Version",
characters = "Characters",
business = "Business",
settings = "Settings",
config = "Config",
chat = "Chat",
appearance = "Appearance",
misc = "Miscellaneous",
oocDelay = "You must wait %s more seconds before using OOC again!",
loocDelay = "You must wait %s more seconds before using LOOC again!",
usingChar = "You are already using this character.",
notAllowed = "You are not allowed to do this!",
itemNoExist = "The item that you requested does not exist!",
cmdNoExist = "That command does not exist!",
charNoExist = "A matching character could not be found!",
plyNoExist = "A matching player could not be found!",
cfgSet = "%s has set \"%s\" to %s.",
drop = "Drop",
dropTip = "Drops this item from your inventory.",
take = "Take",
takeTip = "Take this item and place it in your inventory.",
dTitle = "Unowned Door",
dTitleOwned = "Purchased Door",
dIsNotOwnable = "This door is unownable.",
dIsOwnable = "You can purchase this door by pressing F2.",
dMadeUnownable = "You have made this door unownable.",
dMadeOwnable = "You have made this door ownable.",
dNotAllowedToOwn = "You are not allowed to own this door!",
dSetDisabled = "You have made this door disabled.",
dSetNotDisabled = "You have made this door no longer disabled.",
dSetHidden = "You have made this door hidden.",
dSetNotHidden = "You have made this door no longer hidden.",
dSetParentDoor = "You have set this door as your parent door.",
dCanNotSetAsChild = "You can not set the parent door as a child!",
dAddChildDoor = "You have added a this door as a child.",
dRemoveChildren = "You have removed all of the children for this door.",
dRemoveChildDoor = "You have removed this door from being a child.",
dNoParentDoor = "You do not have a parent door set.",
dOwnedBy = "This door is owned by %s.",
dConfigName = "Doors",
dSetFaction = "This door now belongs to the %s faction.",
dRemoveFaction = "This door no longer belongs to any faction.",
dNotValid = "You are not looking at a valid door!",
canNotAfford = "You can not afford to purchase this!",
dPurchased = "You have purchased this door for %s.",
dSold = "You have sold this door for %s.",
notOwner = "You are not the owner of this!",
invalidArg = "You have provided an invalid value for argument #%s!",
invalidFaction = "The faction you provided could not be found!",
flagGive = "%s has given %s '%s' flags.",
flagGiveTitle = "Give Flags",
flagTake = "%s has taken '%s' flags from %s.",
flagTakeTitle = "Take Flags",
flagNoMatch = "You must have \"%s\" flag(s) to do this action!",
panelAdded = "You have added a panel.",
panelRemoved = "You have removed %d panel(s).",
textAdded = "You have added a text.",
textRemoved = "You have removed %s text(s).",
moneyTaken = "You were given %s.",
moneyGiven = "You have given %s.",
insufficientMoney = "You do not have enough to do this!",
belowMinMoneyDrop = "You cannot drop less than %s.",
businessPurchase = "You purchased %s for %s.",
businessSell = "You sold %s for %s.",
businessTooFast = "Please wait before purchasing another item!",
cChangeModel = "%s changed %s's model to %s.",
cChangeName = "%s changed %s's name to %s.",
cChangeSkin = "%s changed %s's skin to %s.",
cChangeGroups = "%s changed %s's \"%s\" bodygroup to %s.",
cChangeFaction = "%s has transferred %s to the %s faction.",
playerCharBelonging = "This object is your other character's belonging!",
spawnAdd = "You have added a spawn for the %s.",
spawnDeleted = "You have removed %s spawn point(s).",
someone = "Someone",
rgnLookingAt = "Allow the person you are looking at to recognize you.",
rgnWhisper = "Allow those in a whispering range to recognize you.",
rgnTalk = "Allow those in a talking range to recognize you.",
rgnYell = "Allow those in a yelling range to recognize you.",
icFormat = "%s says \"%s\"",
rollFormat = "%s has rolled %s out of %s.",
wFormat = "%s whispers \"%s\"",
yFormat = "%s yells \"%s\"",
sbOptions = "Click to see options for %s.",
spawnAdded = "You added spawn for %s.",
whitelist = "%s has whitelisted %s for the %s faction.",
unwhitelist = "%s has unwhitelisted %s from the %s faction.",
noWhitelist = "You do not have the whitelist for this character!",
charNotWhitelisted = "%s is not whitelisted for the %s faction.",
gettingUp = "You are now getting up...",
wakingUp = "You are regaining consciousness...",
Weapons = "Weapons",
checkout = "Go to Checkout (%s)",
purchase = "Purchase",
purchasing = "Purchasing...",
success = "Success",
buyFailed = "Purchase failed!",
buyGood = "Purchase successful!",
shipment = "Shipment",
shipmentDesc = "This shipment belongs to %s.",
class = "Class",
classes = "Classes",
illegalAccess = "Illegal Access.",
becomeClassFail = "You cannot become a %s!",
becomeClass = "You have become a %s.",
setClass = "You have set %s's class to %s.",
attributeSet = "You set %s's %s to %s.",
attributeNotFound = "You have specified an invalid attribute!",
attributeUpdate = "You added %s's %s by %s.",
noFit = "You do not have enough space for this item!",
itemOwned = "You cannot interact with an item that you own on a different character!",
help = "Help",
commands = "Commands",
doorSettings = "Door Settings",
sell = "Sell",
access = "Access",
locking = "Locking this entity...",
unlocking = "Unlocking this entity...",
modelNoSeq = "Your model does not support this act!",
notNow = "You are not allowed to do this right now!",
faceWall = "You must be facing the wall to do this!",
faceWallBack = "Your back must be facing the wall to do this!",
descChanged = "You have changed your character's description.",
noOwner = "The owner is invalid!",
invalidItem = "You have specified an invalid item!",
invalidInventory = "You have specified an invalid inventory!",
home = "Home",
charKick = "%s kicked char %s.",
charBan = "%s banned the character %s.",
charBanned = "This character is banned.",
charBannedTemp = "This character is temporarily banned.",
playerConnected = "%s has connected to the server.",
playerDisconnected = "%s has disconnected from the server.",
setMoney = "You have set %s's money to %s.",
itemPriceInfo = "You can purchase this item for %s.\nYou can sell this item for %s",
free = "Free",
vendorNoSellItems = "There are no items to sell.",
vendorNoBuyItems = "There are no items to purchase.",
vendorSettings = "Vendor Settings",
vendorUseMoney = "Vendor should use money?",
vendorNoBubble = "Hide vendor bubble?",
category = "Category",
mode = "Mode",
price = "Price",
stock = "Stock",
none = "None",
vendorBoth = "Buy and Sell",
vendorBuy = "Buy Only",
vendorSell = "Sell Only",
maxStock = "Max Stock",
vendorFaction = "Faction Editor",
buy = "Purchase",
vendorWelcome = "Welcome to my store, what can I get you today?",
vendorBye = "Come again soon!",
charSearching = "You are already searching another character!",
charUnBan = "%s has unbanned the character %s.",
charNotBanned = "This character is not banned!",
quickSettings = "Quick Settings",
vmSet = "You have set your voicemail.",
vmRem = "You have removed your voicemail.",
noPerm = "You are not allowed to do this!",
youreDead = "You are Dead",
injMajor = "Seems critically injured",
injLittle = "Seems injured",
chgName = "Change Name",
chgNameDesc = "Enter the character's new name below.",
weaponSlotFilled = "You cannot use another %s weapon!",
equippedBag = "You cannot move a bag that has an equipped item!",
equippedWeapon = "You cannot move a weapon that is currently equipped!",
nestedBags = "You cannot put an inventory inside of a storage inventory!",
outfitAlreadyEquipped = "You're already wearing this kind of outfit!",
useTip = "Uses the item.",
equip = "Equip",
equipTip = "Equips the item.",
unequip = "Unequip",
unequipTip = "Unequips the item.",
consumables = "Consumables",
plyNotValid = "You are not looking at a valid player!",
restricted = "You have been restrained.",
salary = "You have received %s from your salary.",
noRecog = "You do not recognize this person.",
curTime = "The current time is %s.",
vendorEditor = "Vendor Editor",
edit = "Edit",
disable = "Disable",
vendorPriceReq = "Enter the new price for this item.",
vendorEditCurStock = "Edit Current Stock",
vendorStockReq = "Enter the Maximum amount of Stock the item should have.",
vendorStockCurReq = "Enter how many items are available for purchase from the maximum stock.",
you = "You",
vendorSellScale = "Sell price scale",
vendorNoTrade = "You are not able to trade with this vendor!",
vendorNoMoney = "This vendor can not afford that item!",
vendorNoStock = "This vendor does not have that item in stock!",
vendorMaxStock = "This vendor has full stock of that item!",
contentTitle = "Helix Content Missing",
contentWarning = "You do not have the Helix content mounted. This may result in certain features missing.\nWould you like to open the Workshop page for the Helix content?",
flags = "Flags",
mapRestarting = "The map will restart in %d seconds!",
chooseTip = "Choose this character to play with.",
deleteTip = "Delete this character.",
storageInUse = "Someone else is using this!",
storageSearching = "Searching...",
container = "Container",
containerPassword = "You have set this container's password to %s.",
containerPasswordRemove = "You have removed this container's password.",
containerPasswordWrite = "Enter the password.",
containerName = "You have set this container's name to %s.",
containerNameWrite = "Enter the name.",
containerNameRemove = "You have removed this container's name.",
containerInvalid = "You need to be looking at a container to do this!",
wrongPassword = "You have entered an incorrect password!",
passwordAttemptLimit = "You have made too many incorrect password attempts!",
respawning = "Respawning...",
tellAdmin = "Inform a staff member of this error: %s",
syntax = "Syntax: %s",
mapAdd = "You have added a map scene.",
mapDel = "You have removed %d map scene(s).",
mapRepeat = "Now add the secondary point.",
scoreboard = "Scoreboard",
ping = "Ping: %d",
viewProfile = "View Steam Profile",
copySteamID = "Copy Steam ID",
money = "Money",
moneyLeft = "Your Money: ",
currentMoney = "Money Left: ",
invalidClass = "That is not a valid class!",
invalidClassFaction = "That is not a valid class for that faction!",
miscellaneous = "Miscellaneous",
general = "General",
observer = "Observer",
performance = "Performance",
thirdperson = "Third Person",
date = "Date",
interaction = "Interaction",
server = "Server",
resetDefault = "Reset to default",
resetDefaultDescription = "This will reset \"%s\" to its default value of \"%s\".",
optOpenBags = "Open bags with inventory",
optdOpenBags = "Automatically views all the bags in your inventory when the menu is opened.",
optShowIntro = "Show intro on join",
optdShowIntro = "Shows the Helix introduction the next time you join. This is always disabled after you've watched it.",
optCheapBlur = "Disable blurring",
optdCheapBlur = "Replaces UI blurring with simple dimming.",
optObserverTeleportBack = "Return to previous location",
optdObserverTeleportBack = "Returns you to the location that you entered observer mode.",
optObserverESP = "Show admin ESP",
optdObserverESP = "Shows the names and locations of each player in the server.",
opt24hourTime = "Use 24-hour time",
optd24hourTime = "Show timestamps in 24-hour time, rather than 12-hour time (AM/PM).",
optChatNotices = "Show notices in chat",
optdChatNotices = "Puts all notices that appear in the top-right into the chat instead.",
optChatTimestamps = "Show timestamps in chat",
optdChatTimestamps = "Prepends the time to each message in the chatbox.",
optAlwaysShowBars = "Always show info bars",
optdAlwaysShowBars = "Draws the information bars in the top-left, regardless if it should be hidden or not.",
optAltLower = "Hide hands when lowered", -- @todo remove me
optdAltLower = "Hides your hands when they are by your side.", -- @todo remove me
optThirdpersonEnabled = "Enable third person",
optdThirdpersonEnabled = "Moves the camera behind you. This can also be enabled with the \"ix_togglethirdperson\" console command.",
optThirdpersonClassic = "Enable classic third person camera",
optdThirdpersonClassic = "Moves your character's view with your mouse.",
optThirdpersonVertical = "Camera height",
optdThirdpersonVertical = "How high up the camera should be.",
optThirdpersonHorizontal = "Camera offset",
optdThirdpersonHorizontal = "How far left or right the camera should be.",
optThirdpersonDistance = "Camera distance",
optdThirdpersonDistance = "How far away the camera should be.",
optThirdpersonCrouchOffset = "Camera crouch height",
optdThirdpersonCrouchOffset = "How high up the camera should be while crouched.",
optDisableAnimations = "Disable animations",
optdDisableAnimations = "Stops animations from playing to provide instant transitions.",
optAnimationScale = "Animation scale",
optdAnimationScale = "How much faster or slower the animations should play.",
optLanguage = "Language",
optdLanguage = "The language shown in the Helix UI.",
optMinimalTooltips = "Minimal HUD tooltips",
optdMinimalTooltips = "Changes the HUD tooltip style to take up less space.",
optNoticeDuration = "Notice duration",
optdNoticeDuration = "How long to show notices for (in seconds).",
optNoticeMax = "Maximum notices",
optdNoticeMax = "The amount of notices shown before excess previous ones are removed.",
optChatFontScale = "Chat font scale",
optdChatFontScale = "How much bigger or smaller the chat font should be.",
optChatOutline = "Outline chat text",
optdChatOutline = "Draws an outline around the chat text, rather than a drop shadow. Enable this if you are having trouble reading text.",
optEscCloseMenu = "Escape returns to game",
optdEscCloseMenu = "Whether the escape menu should close itself when you're using it to close the in-game menu.",
cmdRoll = "Rolls a number between 0 and the specified number.",
cmdPM = "Sends a private message to someone.",
cmdReply = "Sends a private message to the last person you received a message from.",
cmdSetVoicemail = "Sets or removes the auto-reply message when someone sends you a private message.",
cmdCharGiveFlag = "Gives the specified flag(s) to someone.",
cmdCharTakeFlag = "Removes the specified flag(s) from someone if they have it.",
cmdToggleRaise = "Raises or lowers the weapon you're holding.",
cmdCharSetModel = "Sets a person's character model.",
cmdCharSetSkin = "Sets the skin for a person's character model.",
cmdCharSetBodygroup = "Sets the bodygroup for a person's character model.",
cmdCharSetAttribute = "Sets the level of the specified attribute for someone.",
cmdCharAddAttribute = "Adds a level to the specified attribute for someone.",
cmdCharSetName = "Changes someone's name to the specified name.",
cmdCharGiveItem = "Gives the specified item to someone.",
cmdCharKick = "Forcefully makes someone log off of their character.",
cmdCharBan = "Prohibits someone from logging into the server with their current character.",
cmdCharUnban = "Allows a prohibited character the ability to be used again.",
cmdGiveMoney = "Gives a specified amount of money to the person you are looking at.",
cmdCharSetMoney = "Changes the total amount of money of someone to the amount specified.",
cmdDropMoney = "Drops the specified amount of money in a little box in front of you.",
cmdPlyWhitelist = "Allows someone to create a character in the specified faction.",
cmdCharGetUp = "Attempts to stand back up after having fallen over.",
cmdPlyUnwhitelist = "Disallows someone to create a character in the specified faction.",
cmdCharFallOver = "Makes your knees weak and fall over.",
cmdBecomeClass = "Attempts to become part of the specified class in your current faction.",
cmdCharDesc = "Sets your physical description.",
cmdCharDescTitle = "Physical Description",
cmdCharDescDescription = "Enter your character's physical description.",
cmdPlyTransfer = "Transfers someone to the specified faction.",
cmdCharSetClass = "Forcefully makes someone become part of the specified class in their current faction.",
cmdMapRestart = "Restarts the map after the specified amount of time.",
cmdPanelAdd = "Places a web panel in the world.",
cmdPanelRemove = "Removes a web panel that you are looking at.",
cmdTextAdd = "Places a block of text in the world.",
cmdTextRemove = "Removes blocks of text that you are looking at.",
cmdMapSceneAdd = "Adds a cinematic camera viewpoint that is shown in the character menu.",
cmdMapSceneRemove = "Removes a camera viewpoint that is shown in the character menu.",
cmdSpawnAdd = "Adds a spawn point for the specified faction.",
cmdSpawnRemove = "Removes any spawn points you are looking at.",
cmdAct = "Performs the %s animation.",
cmdContainerSetPassword = "Sets the password of the container you're looking at.",
cmdDoorSell = "Sells the door you are looking at.",
cmdDoorBuy = "Buys the door you are looking at.",
cmdDoorSetUnownable = "Makes the door you are looking at unownable.",
cmdDoorSetOwnable = "Makes the door you are looking at ownable.",
cmdDoorSetFaction = "Makes the door you are looking at owned by the specified faction.",
cmdDoorSetDisabled = "Disallows any commands to be ran on the door you are looking at.",
cmdDoorSetTitle = "Sets the title of the door you are looking at.",
cmdDoorSetParent = "Sets the parent of a pair of doors.",
cmdDoorSetChild = "Sets the child of a pair of doors.",
cmdDoorRemoveChild = "Removes the child from a pair of doors.",
cmdDoorSetHidden = "Hides the description of the door you are looking at, but still allows it to be ownable.",
cmdDoorSetClass = "Makes the door you are looking at owned by the specified class.",
cmdMe = "Perform a physical action.",
cmdIt = "Make something around you perform an action.",
cmdW = "Whisper something to the people near you.",
cmdY = "Yell something to the people around you.",
cmdEvent = "Make something perform an action that everyone can see.",
cmdOOC = "Sends a message in global out-of-character chat.",
cmdLOOC = "Sends a message in local out-of-character chat.",
iconEditorAlignBest = "Best Guess",
iconEditorWidth = "Width",
iconEditorHeight = "Height",
iconEditorCopy = "Copy to Clipboard",
iconEditorCopied = "Copied Item Model Data to Clipboard.",
iconEditorAlignFront = "Align from Front",
iconEditorAlignAbove = "Align from Above",
iconEditorAlignRight = "Align from Right",
iconEditorAlignCenter = "Align from Center"
}
================================================
FILE: gamemode/languages/sh_french.lua
================================================
-- FRENCH TRANSLATION
-- http://steamcommunity.com/id/sadness81
-- https://steamcommunity.com/id/Azphal
NAME = "Français"
LANGUAGE = {
helix = "Helix",
introTextOne = "fist industrie vous presente",
introTextTwo = "en collaboration avec %s",
introContinue = "Appuyez sur ESPACE pour continuer",
helpIdle = "Selectionner une catégorie",
helpCommands = "Les paramètres de commande avec les sont obligatoires, tandis que les [crochets] sont facultatifs.",
helpFlags = "Les drapeaux sur fond vert sont accessibles à ce personnage.",
creditSpecial = "Remerciement spécial",
creditLeadDeveloper = "Développeur principal",
creditUIDesigner = "Concepteur d'interface utilisateur",
creditManager = "Chef de projet",
creditTester = "Testeur en chef",
chatTyping = "Écrit...",
chatTalking = "Parle...",
chatYelling = "Hurle...",
chatWhispering = "Chuchote...",
chatPerforming = "Fait une action...",
chatNewTab = "Nouvel onglet...",
chatReset = "Réinitialise les discussions",
chatResetTabs = "Réinitialiser les onglets",
chatCustomize = "Personnalise...",
chatCloseTab = "Ferme les onglets",
chatTabName = "Nom de l'onglet",
chatAllowedClasses = "Types de chat acceptés",
chatTabExists = "Un onglet de discussion portant ce nom existe déjà !",
chatMarkRead = "Tout marquer comme lu",
community = "Communauté",
checkAll = "Cocher tout",
uncheckAll = "Décocher tout",
color = "Couleur",
type = "Type",
display = "Afficher",
loading = "Chargement",
dbError = "La connexion à la base de données a échouée",
unknown = "Inconnu",
noDesc = "Aucune description disponible",
create = "Créer",
update = "Mettre à jour",
load = "Charger le personnage",
loadTitle = "Charger un personnage",
leave = "Quitter le serveur",
leaveTip = "Quitter le serveur actuel.",
["return"] = "Revenir",
returnTip = "Retourner au menu précédent..",
proceed = "Procéder",
faction = "Faction",
skills = "Compétences",
choose = "Choisir",
chooseFaction = "Choisir une faction",
chooseDescription = "Définissez votre récit",
chooseSkills = "Affûtez vos compétences",
name = "Nom",
description = "Description",
model = "Modèle",
attributes = "Attribut",
attribPointsLeft = "Points restant",
charInfo = "Information du personnage",
charCreated = "Vous avez créé votre personnage avec succès.",
charCreateTip = "Remplissez les champs ci-dessous et appuyez sur 'Terminée' pour créer votre personnage.",
invalid = "Vous avez fourni un invalide %s !",
nameMinLen = "Votre nom doit contenir au moins %d caractères !",
nameMaxLen = "Votre nom ne doit pas dépasser %d caractères !",
descMinLen = "Votre description doit contenir au moins %d caractères !",
maxCharacters = "Vous ne pouvez pas créer plus de personnage !",
player = "Joueur",
finish = "Terminée",
finishTip = "Terminez la création du personnage.",
needModel = "Vous devez choisir un modèle valide !",
creating = "Votre personnage est en train d'être créé...",
unknownError = "Une erreur inconnue s'est produite !",
areYouSure = "Êtes-vous sûr ?",
delete = "Supprimer",
deleteConfirm = "Ce personnage sera irrévocablement supprimé !",
deleteComplete = "%s a été supprimé.",
no = "Non",
yes = "Oui",
close = "Fermer",
save = "Sauvegarder",
itemInfo = "Nom: %s\nDescription: %s",
itemCreated = "Élément(s) créé(s) avec succès.",
cloud_no_repo = "Le référentiel fourni n'est pas valide !",
cloud_no_plugin = "Le plugin fourni n'est pas valide !",
inv = "Inventaire",
plugins = "Plugins",
author = "Auteur",
version = "Version",
characters = "Personnages",
business = "Marché",
settings = "Options",
config = "Configuration",
chat = "Chat",
appearance = "Apparence",
misc = "Divers",
oocDelay = "Vous devez attendre %s seconde(s) avant de réutiliser le OOC !",
loocDelay = "Vous devez attendre %s seconde(s) avant de réutiliser le LOOC !",
usingChar = "Vous utilisez déjà ce personnage.",
notAllowed = "Vous n'êtes pas autorisé à le faire !",
itemNoExist = "L'article que vous avez demandé n'existe pas !",
cmdNoExist = "Cette commande n'existe pas !",
charNoExist = "Un personnage correspondant n'a pas pu être trouvé !",
plyNoExist = "Impossible de trouver un joueur correspondant !",
cfgSet = "%s a mis \"%s\" à %s.",
drop = "Lâcher",
dropTip = "Lâcher cet objet de votre inventaire.",
take = "Prendre",
takeTip = "Prendre cet objet et le placer dans votre inventaire.",
dTitle = "Porte sans propriétaire",
dTitleOwned = "Porte achetée",
dIsNotOwnable = "Cette porte ne peut être achetée.",
dIsOwnable = "Vous pouvez acheter cette porte en appuyant sur F2.",
dMadeUnownable = "Vous avez rendu cette porte inachetable.",
dMadeOwnable = "Vous avez rendu cette porte achetable.",
dNotAllowedToOwn = "Vous n'êtes pas autorisé à posséder cette porte !",
dSetDisabled = "Vous avez désactivé cette porte.",
dSetNotDisabled = "Vous avez réactivé cette porte.",
dSetHidden = "Vous avez caché cette porte.",
dSetNotHidden = "Vous avez montré cette porte.",
dSetParentDoor = "Vous avez défini cette porte comme porte parent.",
dCanNotSetAsChild = "Vous ne pouvez pas définir une porte héritière alors qu'elle est parente !",
dAddChildDoor = "Vous avez défini cette porte héritère.",
dRemoveChildren = "Vous avez enlevé tout les héritiers de cette porte.",
dRemoveChildDoor = "Vous avez enlevé cette porte de son status hértier.",
dNoParentDoor = "Aucune porte parent existe.",
dOwnedBy = "Cette porte appartient à %s.",
dConfigName = "Portes",
dSetFaction = "Cette porte appartient désormais à la faction %s.",
dRemoveFaction = "Cette porte n'appartient plus à aucune faction.",
dNotValid = "Vous ne regardez pas une porte valide !",
canNotAfford = "Vous ne pouvez pas vous permettre d'acheter ceci !",
dPurchased = "Vous avez acheté cette porte pour %s.",
dSold = "Vous avez vendu cette porte pour %s.",
notOwner = "Vous n'êtes pas le propriétaire de cela !",
invalidArg = "Vous avez fourni une valeur non valide pour l'argument #%s !",
invalidFaction = "La faction que vous avez fournie est introuvable !",
flagGive = "%s a donné %s '%s' flags.",
flagGiveTitle = "Donner Flags",
flagTake = "%s a pris les '%s' flags de %s.",
flagTakeTitle = "Prend les flags",
flagNoMatch = "Vous devez avoir \"%s\" flag(s) pour faire cette action !",
textAdded = "Vous avez ajouté un texte.",
textRemoved = "Vous avez supprimé le texte de %s.",
moneyTaken = "Vous avez reçu %s.",
moneyGiven = "Vous avez donné %s.",
insufficientMoney = "Tu n'as pas assez pour faire ça !",
businessPurchase = "Vous avez acheté %s pour %s.",
businessSell = "Vous avez vendu %s pour %s.",
businessTooFast = "Veuillez patienter avant d'acheter un autre article !",
cChangeModel = "%s a changé le model de %s en %s.",
cChangeName = "%s a changé le nom de %s en %s.",
cChangeSkin = "%s a changé le skin de %s en %s.",
cChangeGroups = "%s a changé le Bodygroup de %s \"%s\" en %s.",
cChangeFaction = "%s a transféré %s dans la faction %s.",
playerCharBelonging = "Cet objet appartient à votre autre personnage !",
spawnAdd = "Vous avez ajouté un spawn pour le %s.",
spawnDeleted = "Vous avez supprimé le point d'apparition de %s.",
someone = "Quelqu'un",
rgnLookingAt = "Permettre à la personne que vous regardez de vous reconnaître.",
rgnWhisper = "Permettre à ceux qui entendent vos chuchotements de vous reconnaître.",
rgnTalk = "Permettre à ceux qui entendent vos conversations de vous reconnaître.",
rgnYell = "Permettre à ceux qui entendent vos cris de vous reconnaître.",
icFormat = "%s dit \"%s\"",
rollFormat = "%s fait un roll de %s.",
wFormat = "%s chuchote \"%s\"",
yFormat = "%s hurle \"%s\"",
sbOptions = "Cliquez pour voir les options pour %s.",
spawnAdded = "Vous avez ajouté un spawn pour %s.",
whitelist = "%s a mis dans la whitelist %s de la faction %s.",
unwhitelist = "%s a enlevé de la whitelist %s de la faction %s.",
noWhitelist = "vous n'avez pas la whitelist pour ce personnage !",
gettingUp = "Vous êtes maintenant relevé...",
wakingUp = "Vous reprenez conscience...",
Weapons = "Arme",
checkout = "Aller au Checkpoint (%s)",
purchase = "Achat",
purchasing = "Achat...",
success = "Succès",
buyFailed = "Achat raté !",
buyGood = "Achat réussi !",
shipment = "Expédition",
shipmentDesc = "Cet envoi appartient à %s.",
class = "Classe",
classes = "Classes",
illegalAccess = "Accès illégal.",
becomeClassFail = "Vous ne pouvez pas devenir un %s!",
becomeClass = "Vous êtes devenu un %s.",
setClass = "Vous avez défini la classe de %s en %s.",
attributeSet = "Vous avez définis %s's %s en %s.",
attributeNotFound = "Vous avez spécifié un attribut non valide !",
attributeUpdate = "Vous avez ajouté %s's %s par %s.",
noFit = "Vous n'avez pas assez d'espace pour cet article !",
itemOwned = "Vous ne pouvez pas interagir avec un objet que vous possédez sur un personnage différent !",
help = "Aide",
commands = "Commande",
doorSettings = "Réglages de la porte",
sell = "Vendre",
access = "Accès",
locking = "VERROUILLAGE DE L'ENTITÉ...",
unlocking = "DÉVERROUILLAGE DE L'ENTITÉ...",
modelNoSeq = "Votre modèle ne supporte pas cet action.",
notNow = "Vous n'êtes pas autorisé à le faire ceci maintenant.",
faceWall = "Vous devez être face au mur pour faire ceci.",
faceWallBack = "Votre dos doit faire face au mur pour faire ceci.",
descChanged = "Vous avez changé la description de votre personnage.",
noOwner = "Le propriétaire est invalide !",
invalidItem = "Vous avez spécifié un élément non valide !",
invalidInventory = "Vous avez spécifié un inventaire non valide !",
home = "Maison",
charKick = "%s a exclu le personnage %s.",
charBan = "%s a banni le personnage %s.",
charBanned = "Ce personnage est banni.",
playerConnected = "%s s'est connecté au serveur.",
playerDisconnected = "%s s'est déconnecté du serveur.",
setMoney = "Vous avez défini l'argent de %s à %s..",
itemPriceInfo = "Vous pouvez acheter cet article pour %s.\nVous pouvez vendre cet article pour %s",
free = "Gratuit",
vendorNoSellItems = "Il n'y a pas d'objets à vendre.",
vendorNoBuyItems = "Il n'y a pas d'objets à acheter.",
vendorSettings = "Paramètres du vendeur",
vendorUseMoney = "Le vendeur doit utiliser de l'argent ?",
vendorNoBubble = "Masquer la bulle du vendeur ?",
mode = "Mode",
price = "Prix",
stock = "Stock",
none = "Aucun",
vendorBoth = "Acheter et vendre",
vendorBuy = "Achat seulement",
vendorSell = "Vente seulement",
maxStock = "Stock max",
vendorFaction = "Éditeur de faction",
buy = "Achat",
vendorWelcome = "Bienvenue dans mon magasin, que puis-je vous offrir aujourd'hui ?",
vendorBye = "Reviens bientôt !",
charSearching = "Vous recherchez déjà un autre personnage !",
charUnBan = "%s a débanni le personnage %s.",
charNotBanned = "Ce personnage n'est pas banni!",
quickSettings = "Réglages rapides",
vmSet = "Vous avez configuré votre messagerie vocale.",
vmRem = "Vous avez supprimé votre messagerie vocale.",
noPerm = "Vous n'êtes pas autorisé à faire ceci!",
youreDead = "Tu es mort",
injMajor = "Semble gravement blessé",
injLittle = "Semble blessé",
chgName = "Changer de nom",
chgNameDesc = "Entrez le nouveau nom du personnage ci-dessous.",
weaponSlotFilled = "Vous ne pouvez pas utiliser une autre arme %s!",
equippedBag = "Vous ne pouvez pas déplacer un sac contenant un objet équipé !",
equippedWeapon = "Vous ne pouvez pas déplacer une arme actuellement équipée !",
nestedBags = "Vous ne pouvez pas mettre un inventaire à l'intérieur d'un inventaire de stockage !",
outfitAlreadyEquipped = "Tu portes déjà ce genre de tenue !",
useTip = "Utiliser l'objet.",
equip = "Équiper",
equipTip = "Équiper l'objet.",
unequip = "Déséquiper",
unequipTip = "Déséquiper l'objet.",
consumables = "Consommables",
plyNotValid = "Vous ne regardez pas un joueur valide !",
restricted = "Vous avez été restreint.",
salary = "Vous avez reçu %s de votre salaire.",
noRecog = "Vous ne reconnaissez pas cette personne.",
curTime = "L'heure actuelle est %s.",
vendorEditor = "Éditeur du vendeur",
edit = "Edit",
disable = "Désactiver",
vendorPriceReq = "Entrez le nouveau prix pour cet article.",
vendorEditCurStock = "Modifier le stock actuel",
you = "Vous",
vendorSellScale = "Échelle de prix",
vendorNoTrade = "Vous ne pouvez pas échanger avec ce marchand !",
vendorNoMoney = "Ce marchand ne peut pas se acheter cet article !",
vendorNoStock = "Ce marchand n'a pas cet article en stock !",
contentTitle = "Contenu Helix manquant",
contentWarning = "Le contenu d'Helix n'est pas monté. Certaines fonctionnalités peuvent être manquantes.\nSouhaitez-vous ouvrir la page du Workshop pour le contenu Helix ?",
flags = "Flags",
mapRestarting = "La carte va redémarrer dans %d secondes !",
chooseTip = "Choisissez ce personnage jouer avec.",
deleteTip = "Supprimer ce personnage.",
storageInUse = "Quelqu'un d'autre utilise ceci !",
storageSearching = "Recherche...",
container = "Conteneur",
containerPassword = "Vous avez défini le mot de passe de ce conteneur en %s.",
containerPasswordRemove = "Vous avez supprimé le mot de passe de ce conteneur.",
containerPasswordWrite = "Entrer le mot de passe.",
containerName = "Vous avez défini le nom de ce conteneur en %s.",
containerNameWrite = "Entrez le nom.",
containerNameRemove = "Vous avez supprimé le nom de ce conteneur.",
containerInvalid = "Vous devez regarder un conteneur pour faire ceci !",
wrongPassword = "Vous avez entré un mot de passe incorrect !",
respawning = "Réapparition...",
scoreboard = "Tableau des joueurs",
ping = "Ping: %d",
viewProfile = "Voir le profil Steam",
copySteamID = "Copier le Steam ID",
money = "Argent",
moneyLeft = "Votre argent: ",
currentMoney = "Argent restant: ",
invalidClass = "Ceci n'est pas une classe valide !",
invalidClassFaction = "Il ne s'agit pas d'une classe valide pour cette faction !",
miscellaneous = "Divers",
general = "General",
observer = "Observateur",
performance = "Performance",
thirdperson = "Troisième personne",
date = "Date",
interaction = "Interaction",
server = "Serveur",
resetDefault = "Réinitialiser aux valeurs par défaut",
resetDefaultDescription = "Ceci réinitialisera \"%s\" à sa valeur par défaut de \"%s\".",
optOpenBags = "Sacs ouverts avec inventaire",
optdOpenBags = "Visualise automatiquement tous les sacs de votre inventaire lorsque le menu est ouvert.",
optShowIntro = "Afficher l'intro au démarrage",
optdShowIntro = "Affiche l'introduction de Helix la prochaine fois que vous vous inscrivez. Ceci est toujours désactivé après l'avoir regardé.",
optCheapBlur = "Désactiver le flou",
optdCheapBlur = "Remplace le flou de l'interface utilisateur par une simple gradation.",
optObserverTeleportBack = "Retour à l'emplacement précédent",
optdObserverTeleportBack = "Vous ramène à l'emplacement où vous êtes entré en mode observateur.",
optObserverESP = "Montrer l'ESP admin",
optdObserverESP = "Affiche les noms et les emplacements de chaque joueur sur le serveur.",
opt24hourTime = "Utilisez le temps de 24 heures",
optd24hourTime = "Afficher les horodatages sur 24 heures au lieu de 12 heures (AM / PM).",
optChatNotices = "Afficher les avis dans le chat",
optdChatNotices = "Met à la place tous les avis qui apparaissent en haut à droite dans le chat.",
optChatTimestamps = "Afficher les horodatages dans le chat",
optdChatTimestamps = "Prépose l'heure à chaque message dans la chatbox.",
optAlwaysShowBars = "Toujours afficher les barres d'informations",
optdAlwaysShowBars = "Affiche les barres d’information en haut à gauche, qu’elles soient masquées ou non.",
optAltLower = "Masquer les mains une fois abaissé", -- @todo remove me
optdAltLower = "Cacher les mains quand elles sont sur les côtés.", -- @todo remove me
optThirdpersonEnabled = "Activer la troisième personne",
optdThirdpersonEnabled = "Déplace la caméra derrière toi. Ceci peut également être activé dans la console avec la commande \"ix_togglethirdperson\"",
optThirdpersonClassic = "Activer la caméra classique à la troisième personne",
optdThirdpersonClassic = "Déplace la vue de ton personnage avec ta souris.",
optThirdpersonVertical = "Troisième personne hauteur",
optdThirdpersonVertical = "À quelle hauteur la caméra devrait être.",
optThirdpersonHorizontal = "Troisième personne horizontale",
optdThirdpersonHorizontal = "Quelle distance gauche ou droite la caméra doit être.",
optThirdpersonDistance = "Troisième personne distance",
optdThirdpersonDistance = "À quelle distance la caméra doit être.",
optDisableAnimations = "Désactiver les animations",
optdDisableAnimations = "Arrête la lecture des animations pour fournir des transitions instantanées.",
optAnimationScale = "Échelle d'animation",
optdAnimationScale = "A quelle vitesse les animations doivent être faites.",
optLanguage = "Langue",
optdLanguage = "La langue affichée dans l'interface utilisateur Helix.",
optNoticeDuration = "Durée de la notification",
optdNoticeDuration = "Combien de temps afficher les notifications pour (en secondes).",
optNoticeMax = "Notification maximum",
optdNoticeMax = "Le nombre de notification affichés avant que les précédentes soient supprimés.",
optChatFontScale = "Échelle de polices de discussion",
optdChatFontScale = "A quelle taille doit être la police de discussion.",
optChatOutline = "Décrire le texte du chat",
optdChatOutline = "Affiche un contour autour du texte de la discussion plutôt qu’une ombre. Activez cette option si vous rencontrez des difficultés pour lire du texte.",
cmdRoll = "Rolls un nombre entre 0 et le nombre spécifié",
cmdPM = "Envoie un message privé à quelqu'un.",
cmdReply = "Envoie un message privé à la dernière personne à qui vous avez envoyé un message.",
cmdSetVoicemail = "Définit ou supprime le message de réponse automatique lorsque quelqu'un vous envoie un message privé.",
cmdCharGiveFlag = "Donne le(s) flag(s) spécifié(s) à quelqu'un.",
cmdCharTakeFlag = "Supprime le flag spécifié de la personnage si elle le possède.",
cmdToggleRaise = "Lève ou baisse l'arme que vous tenez.",
cmdCharSetModel = "Définit le modèle de personnage d'une personne.",
cmdCharSetSkin = "Définit l'apparence du modèle de personnage d'une personne.",
cmdCharSetBodygroup = "Définit le Bodygroup pour le modèle de personnage d'une personne.",
cmdCharSetAttribute = "Définit le niveau de l'attribut spécifié pour quelqu'un.",
cmdCharAddAttribute = "Ajoute un niveau à l'attribut spécifié pour quelqu'un.",
cmdCharSetName = "Remplace le nom de quelqu'un par le nom spécifié.",
cmdCharGiveItem = "Donne l'élément spécifié à quelqu'une.",
cmdCharKick = "Oblige quelqu'un à se déconnecter de son personnage.",
cmdCharBan = "Interdit à une personne de jouer son personnage actuel.",
cmdCharUnban = "Permet à un personnage banni d'être utiliser à nouveau.",
cmdGiveMoney = "Donne une somme d'argent précise à la personne que vous regardez.",
cmdCharSetMoney = "Modifie le montant total d'argent d'une personne en fonction du montant spécifié.",
cmdDropMoney = "Dépose le montant d'argent spécifié dans une petite boîte devant vous.",
cmdPlyWhitelist = "Permet à quelqu'un de créer un personnage dans la faction spécifiée.",
cmdCharGetUp = "Tentatives de se relever après être tombé.",
cmdPlyUnwhitelist = "Interdit à quelqu'un de créer un personnage dans la faction spécifiée.",
cmdCharFallOver = "Rends vos genoux faibles et vous fait tomber.",
cmdBecomeClass = "Tente de faire partie de la classe spécifiée dans votre faction actuelle.",
cmdCharDesc = "Définir votre description physique",
cmdCharDescTitle = "Description physique",
cmdCharDescDescription = "Entrez la description physique de votre personnage.",
cmdPlyTransfer = "Transfère quelqu'un à la faction spécifiée.",
cmdCharSetClass = "Force quelqu'un à faire partie d'une classe dans sa faction.",
cmdMapRestart = "Redémarre la carte après le délai spécifié.",
cmdPanelAdd = "Place un panneau Web dans le monde.",
cmdPanelRemove = "Supprime un panneau Web que vous regardez.",
cmdTextAdd = "Place un bloc de texte dans le monde.",
cmdTextRemove = "Supprime les blocs de texte que vous regardez.",
cmdMapSceneAdd = "Ajoute un point de vue de caméra cinématique affiché dans le menu du personnage..",
cmdMapSceneRemove = "Supprime un point de vue de la caméra qui est affiché dans le menu du personnage.",
cmdSpawnAdd = "Ajoute un point d'apparition pour la faction spécifiée.",
cmdSpawnRemove = "Supprime les points d'apparition que vous regardez.",
cmdAct = "Effectuer l'animation %s.",
cmdContainerSetPassword = "Définit le mot de passe du conteneur que vous consultez.",
cmdDoorSell = "Vend la porte que vous regardez.",
cmdDoorBuy = "Achète la porte que vous regardez.",
cmdDoorSetUnownable = "Rend la porte que vous regardez inachetable.",
cmdDoorSetOwnable = "Rend la porte que vous regardez achetable.",
cmdDoorSetFaction = "Attribut la porte que vous regardez à la faction spécifiée.",
cmdDoorSetDisabled = "N'autorise aucune commande sur la porte que vous regardez.",
cmdDoorSetTitle = "Définit le titre de la porte que vous regardez.",
cmdDoorSetParent = "Définit le parent d'une paire de portes.",
cmdDoorSetChild = "Définit l'héritié d'une paire de portes.",
cmdDoorRemoveChild = "Enlève l'héritié d'une paire de portes.",
cmdDoorSetHidden = "Cache la description de la porte que vous regardez, mais lui permet quand même d'être achetable.",
cmdDoorSetClass = "Attribut la porte que vous regardez à la classe spécifiée.",
cmdMe = "Effectuer une action physique.",
cmdIt = "Décrire une action/situation en tant que narrateur.",
cmdW = "Chuchoter quelque chose au gens près de vous.",
cmdY = "Crier quelque chose au gens autour de vous.",
cmdEvent = "Décrire une action que tout le monde peut voir.",
cmdOOC = "Envoie un message dans le chat général en dehors du Rôleplay.",
cmdLOOC = "Envoie un message dans le chat local en dehors du Rôleplay."
}
================================================
FILE: gamemode/languages/sh_german.lua
================================================
-- GERMAN TRANSLATION
NAME = "Deutsch"
LANGUAGE = {
helix = "Helix",
introTextOne = "fist industries präsentiert",
introTextTwo = "in Zusammenarbeit mit %s",
introContinue = "Drücke Leertaste um fortzufahren",
helpIdle = "Wähle eine Kategorie",
helpCommands = "Kommandoparameter mit sind Voraussetung, während [Klammern] optional sind.",
helpFlags = "Dieser Charakter kann auf Flags mit einem grünen Hintergrund zugreifen.",
creditSpecial = "Vielen Dank",
creditLeadDeveloper = "Lead Developer",
creditUIDesigner = "UI Designer",
creditManager = "Project Manager",
creditTester = "Lead Tester",
chatTyping = "Am Schreiben...",
chatTalking = "Am Reden...",
chatYelling = "Am Schreien...",
chatWhispering = "Am Flüstern...",
chatPerforming = "Am Ausführen...",
chatNewTab = "Neuer Tab...",
chatReset = "Position zurücksetzen",
chatResetTabs = "Tabs zurücksetzen",
chatCustomize = "Individualisieren...",
chatCloseTab = "Tab schließen",
chatTabName = "Tab Name",
chatNewTabTitle = "Neuer Tab",
chatAllowedClasses = "Erlaubte Chatklassen",
chatTabExists = "Ein Chat-Tab mit diesem Namen exisitert bereits!",
chatMarkRead = "Alles als gelesen markieren.",
community = "Community",
checkAll = "Alles auswählen",
uncheckAll = "Auswahl aufheben",
color = "Farbe",
type = "Typ",
display = "Anzeige",
loading = "Am Laden",
dbError = "Datenbankverbindung fehlgeschlagen",
unknown = "Unbekannt",
noDesc = "Keine Beschreibung verfügbar",
create = "Erstellen",
update = "Update",
load = "Charakter laden",
loadTitle = "Lade einen Charakter",
leave = "Server verlassen",
leaveTip = "Verlasse den aktuellen Server",
["return"] = "Zurück",
returnTip = "Gehe zum vorherigen Menü zurück",
proceed = "Weiter",
faction = "Fraktion",
skills = "Fähigkeiten",
choose = "Auswählen",
chooseFaction = "Wähle eine Fraktion",
chooseDescription = "Definiere deine Geschichte",
chooseSkills = "Verfeinere deine Fähigkeiten",
name = "Name",
description = "Beschreibung",
model = "Model",
attributes = "Attribute",
attribPointsLeft = "Verbleibende Punkte",
charInfo = "Charakter Informationen",
charCreated = "Charakter erfolgreich erstellt.",
charCreateTip = "Vervollständige alle Felder und klicke Anschließend auf 'Bestätigen' um deinen Charakter zu erstellen.",
invalid = "Falscher %s!",
nameMinLen = "Dein Name muss mindestens %d Zeichen enthalten!",
nameMaxLen = "Deine Name darf maximal %d Zeichen enthalten!",
descMinLen = "Deine Beschreibung muss mindestens %d Zeichen enthalten!",
maxCharacters = "Du kannst keine weiteren Charakter erstellen!",
player = "Spieler",
finish = "Bestätigen",
finishTip = "Beende die Charaktererstellung",
needModel = "Du musst ein gültiges Model auswählen!",
creating = "Dein Charakter wird erstellt...",
unknownError = "Ein unbekannter Fehler ist aufgetreten!",
areYouSure = "Bist du dir sicher?",
delete = "Löschen",
deleteConfirm = "Dieser Charakter kann nicht wiederhergestellt werden!",
deleteComplete = "%s wurde gelöscht.",
no = "Nein",
yes = "Ja",
close = "Schließen",
save = "Speichern",
itemInfo = "Name: %s\nBeschreibung: %s",
itemCreated = "Item(s) erfolgreich erstellt.",
cloud_no_repo = "Das gewählte Lager ist nicht gültig!",
cloud_no_plugin = "Das gewählte Plugin ist nicht gültig!",
inv = "Inventar",
plugins = "Plugins",
pluginLoaded = "%s hat Plugin \"%s\" für das Laden eines Mapchange aktiviert.",
pluginUnloaded = "%s hat Plugin \"%s\" für das Laden eines Mapchange deaktivier.",
loadedPlugins = "Geladene Plugins",
unloadedPlugins = "Nicht-Geladene Plugins",
on = "Aktiviert",
off = "Deaktiviert",
author = "Author",
version = "Version",
characters = "Charaktere",
business = "Geschäft",
settings = "Einstellungen",
config = "Konfiguration",
chat = "Chat",
appearance = "Aussehen",
misc = "Sonstiges",
oocDelay = "Du musst %s Sekunden warten bevor du OOC erneut benutzt!",
loocDelay = "Du musst %s Sekunden warten bevor du LOOC erneut benutzt!",
usingChar = "Du benutzt diesen Charakter schon.",
notAllowed = "Das darfst du nicht tun!",
itemNoExist = "Das angefragte Item existiert nicht!",
cmdNoExist = "Dieser Befehl existiert nicht!",
charNoExist = "Ein passendes Zeichen wurde nicht gefunden!",
plyNoExist = "Ein passendes Spieler wurde nicht gefunden!",
cfgSet = "%s hat \"%s\" zu %s gesetzt.",
drop = "Fallenlassen",
dropTip = "Lasse diese Item fallen.",
take = "Nehmen",
takeTip = "Nehme dieses Item und packe es in dein Inventar.",
dTitle = "Unbekannte Tür",
dTitleOwned = "Gekaufte Tür",
dIsNotOwnable = "Tür nicht verfügbar",
dIsOwnable = "Du kannst diese Tür mit F2 kaufen.",
dMadeUnownable = "Diese Tür ist nun nicht mehr verfügbar.",
dMadeOwnable = "Diese Tür ist nun wieder verfügbar.",
dNotAllowedToOwn = "Du darfst diese Tür nicht besitzen!",
dSetDisabled = "Diese Tür ist nun deaktiviert.",
dSetNotDisabled = "Diese Tür ist nun wieder aktiviert.",
dSetHidden = "Diese Tür ist nun versteckt.",
dSetNotHidden = "Diese Tür ist nun sichtbar.",
dSetParentDoor = "Du hast diese Tür als deine Parent Tür gesetzt",
dCanNotSetAsChild = "Du kannst diese Parent-Tür nicht als Child setzen.",
dAddChildDoor = "Du hast diese Tür als Child gesetzt.",
dRemoveChildren = "Du hast alle Children dieser Tür entfernt.",
dRemoveChildDoor = "Diese Tür ist kein Child mehr.",
dNoParentDoor = "Du hast keine Parent-Tür ausgewählt.",
dOwnedBy = "%s besitzt diese Tür bereits.",
dConfigName = "Türen",
dSetFaction = "Fraktion %s besitzt diese Tür.",
dRemoveFaction = "Diese Tür gehört nun keine Fraktion mehr.",
dNotValid = "Du gucks auf keine Tür!",
canNotAfford = "Du hast nicht genug Geld!",
dPurchased = "Tür wurde für %s von dir gekauft.",
dSold = "Du hast diese Tür für %s verkauft.",
notOwner = "Du bist nicht der Besitzer!",
invalidArg = "Ungültiger Wert für Argument #%s!",
invalidFaction = "Fraktion konnte nicht gefunden werden!",
flagGive = "%s hat %s '%s' Berechtigungen gegeben.",
flagGiveTitle = "Berechtigungen geben",
flagTake = "%s hat '%s' Berechtigungen von %s genommen.",
flagTakeTitle = "Berechtigungen nehmen",
flagNoMatch = "Du benötigt \"%s\" Berechtigung(en) um diese Aktion auszuführen!",
panelAdded = "Feld hinzugefügt.",
panelRemoved = "Du hast %d Feld(er) entfernt.",
textAdded = "Du hast einen Text hinzugefügt.",
textRemoved = "Du hast %s Text(e) gelöscht.",
moneyTaken = "Dir wurde %s gegeben.",
moneyGiven = "Du hast %s gegeben.",
insufficientMoney = "Du hast nicht genug Geld!",
businessPurchase = "Du hast %s für %s gekauft.",
businessSell = "Du hast %s für %s verkauft.",
businessTooFast = "Bitte warte bevor du ein weitere Item kaufst!",
cChangeModel = "%s hat %s's Model zu %s geändert.",
cChangeName = "%s hat %s's Name zu %s geändert.",
cChangeSkin = "%s hat %s's Skin zu %s geändert.",
cChangeGroups = "%s hat %s's \"%s\" Bodygroup zu %s geändert.",
cChangeFaction = "%s hat %s zu Fraktion %s gewechselt.",
playerCharBelonging = "Dieser Gegenstand gehört deinem anderen Charakter!",
spawnAdd = "Du hast einen Spawn für %s hinzugefügt.",
spawnDeleted = "Du hast %s Spawnpoint(s) entfernt.",
someone = "Jemand",
rgnLookingAt = "Erlaube allen in Sichtreichweite dich zu erkennen",
rgnWhisper = "Erlaube allen in Flüsterreichweite dich zu erkennen.",
rgnTalk = "Erlaube allen in Sprachreichweite dich zu erkennen",
rgnYell = "Erlaube allen in Rufreichweite dich zu erkennen",
icFormat = "%s sagt \"%s\"",
rollFormat = "%s hat eine %s von %s gewürfelt.",
wFormat = "%s flüstert \"%s\"",
yFormat = "%s ruft \"%s\"",
sbOptions = "Auswählen um Optionen für %s zu sehen.",
spawnAdded = "Du hast einen Spawn für %s hinzugefügt.",
whitelist = "%s hat %s auf die Whitelist der Fraktion %s gesetzt.",
unwhitelist = "%s hat %s von der Whitelist der Fraktion %s entfernt.",
noWhitelist = "Du hast keine Whitelist für diesen Charakter!",
charNotWhitelisted = "%s ist nicht auf der Whitelist der Fraktion %s.",
gettingUp = "Du stehst auf...",
wakingUp = "Du bist wieder bei Bewusstsein...",
Weapons = "Waffen",
checkout = "Guck dir (%s) an",
purchase = "Kaufen",
purchasing = "Kaufprozess im Gange...",
success = "Erfolgreich",
buyFailed = "Kauf fehlgeschlagen!",
buyGood = "Kauf erfolgreich!",
shipment = "Lieferung",
shipmentDesc = "Diese Lieferung gehört %s.",
class = "Klasse",
classes = "Klassen",
illegalAccess = "Unerlaubter Zugriff.",
becomeClassFail = "Du kannst kein %s werden!",
becomeClass = "Du bist nun %s.",
setClass = "Du hast %s's Klasse zu %s gesetzt.",
attributeSet = "Du hast %s's %s zu %s gesetzt.",
attributeNotFound = "Du hast ein ungültiges Attribut angegeben!",
attributeUpdate = "Du hast %s's %s %s hinzugefügt.",
noFit = "Du hast kein Platz für dieses Item.",
itemOwned = "Du kannst nicht mit einem Item interagieren das ein anderer deiner Charakter besitzt!",
help = "Hilfe",
commands = "Commands",
doorSettings = "Türeinstellungen",
sell = "Verkaufen",
access = "Zugriff",
locking = "Entity am zuschließen...",
unlocking = "Entity am aufschließen...",
modelNoSeq = "Dein Model unterstütz diese Aktion nicht!",
notNow = "Das kannst du nicht tun!",
faceWall = "Du musst mit dem Gesicht zu einer Wand stehen!",
faceWallBack = "Du musst mit dem Rücken zu einer Wand stehen!",
descChanged = "Du hast deine Charakterbeschreibung geändert.",
noOwner = "Ungültiger Besitzer!",
invalidItem = "Ungültiges Item ausgewählt!",
invalidInventory = "Ungültiges Inventar ausgewählt!",
home = "Start",
charKick = "%s hat Charakter %s gekickt.",
charBan = "%s hat Charakter %s gebannt.",
charBanned = "Dieser Charakter ist gebannt.",
charBannedTemp = "Dieser Charakter ist temporär gebannt.",
playerConnected = "%s hat den Server betreten.",
playerDisconnected = "%s hat den Server verlassen.",
setMoney = "Du hast %s's Geld auf %s gesetzt.",
itemPriceInfo = "Du kannst das Item für %s kaufen.\nDu kannst das Item für %s verkaufen.",
free = "Kostenlos",
vendorNoSellItems = "Es gibt keine Items zu verkaufen.",
vendorNoBuyItems = "Es gibt keine Items zu kaufen.",
vendorSettings = "Verkäufereinstellungen",
vendorUseMoney = "Soll der Verkäufer Geld benutzen?",
vendorNoBubble = "Blase verstecken?",
category = "Kategorie",
mode = "Modus",
price = "Preis",
stock = "Bestand",
none = "Keine",
vendorBoth = "Kauf und Verkauf",
vendorBuy = "Nur Kauf",
vendorSell = "Nur Verkauf",
maxStock = "Höchster Bestand",
vendorFaction = "Fraktionseditor",
buy = "Kaufen",
vendorWelcome = "Wilkommen in meinem Laden, wie kann ich behilflich sein?",
vendorBye = "Auf Wiedersehen!",
charSearching = "Du suchst bereits einen anderen Charakter!",
charUnBan = "%s hat Charakter %s entbannt.",
charNotBanned = "Dieser Charakter ist nicht gebannt!",
quickSettings = "Schnelleinstellungen",
vmSet = "Du hast deine Sprachmitteilung gesetzt.",
vmRem = "Du hast deiner Sprachmitteilung entfernt.",
noPerm = "Das ist dir nicht gestattet!",
youreDead = "Du bist tot",
injMajor = "Wirkt schwer verletzt",
injLittle = "Wirkt leicht verletzt",
chgName = "Namen ändern",
chgNameDesc = "Gebe den Charakternamen unten ein.",
weaponSlotFilled = "Du kannst keine weitere %s Waffe benutzen!",
equippedBag = "Du kannst keine Tasche mit einen ausgerüsteten Item bewegen!",
equippedWeapon = "Du kannst keine ausgerüstete Waffe bewegen!",
nestedBags = "Du kannst kein Inventar in ein anderes Inventar legen!",
outfitAlreadyEquipped = "Du trägst dieses Outfit bereits!",
useTip = "Benutze das Item.",
equipTip = "Rüste das Item aus.",
unequipTip = "Lege das Item ab.",
consumables = "Verbrauchsgegenstand",
plyNotValid = "Du schaust nicht auf einen gültigen Spieler!",
restricted = "Du wurdest festgenommen.",
salary = "Du hast %s Lohn bekommen.",
noRecog = "Du erkennst diese Person nicht.",
curTime = "Zeit: %s",
vendorEditor = "Verkäufer Einstellungen",
edit = "Bearbeiten",
disable = "Deaktivieren",
vendorPriceReq = "Neuen Preis eingeben.",
vendorEditCurStock = "Bestand bearbeiten",
vendorStockReq = "Maximalen Bestand für diese Item eingeben.",
vendorStockCurReq = "Gebe ein wie viele Items benötigt werden um vom Bestand kaufen zu dürfen.",
you = "Du",
vendorSellScale = "Verkaufspreis Faktor",
vendorNoTrade = "Du kannst mit diesem Verkäufer nicht tauschen!",
vendorNoMoney = "Dieser Verkäufer kann sich diese Item nicht leiten!",
vendorNoStock = "Dieser Verkäufer hat das Item nicht mehr!",
vendorMaxStock = "Dieser Verkäufer hat einen vollen Bestand dieses Items!",
contentTitle = "Helix Content fehlt!",
contentWarning = "Du hast die Helix-Inhalte nicht installiert. Dies kann dazu führen, dass bestimmte Funktionen fehlen.\nMöchtest du die Workshop-Seite für die Helix-Inhalte öffnen?",
flags = "Berechtigungen",
mapRestarting = "Die Map wird in %d Sekunden neustarten!",
chooseTip = "Wähle einen Charakter zum spielen.",
deleteTip = "Lösche diesen Charakter.",
storageInUse = "Gegenstand in Nutzung!",
storageSearching = "Suche...",
container = "Behälter",
containerPassword = "Passwort auf %s gesetzt.",
containerPasswordRemove = "Du hast das Passwort entfernt.",
containerPasswordWrite = "Passwort eingeben.",
containerName = "Namen des Behälters auf %s gesetzt.",
containerNameWrite = "Namen eingeben.",
containerNameRemove = "Name des Behälters entfernt.",
containerInvalid = "Du musst einen Behälter anschauen!",
wrongPassword = "Falsches Passwort!",
respawning = "Wiederbeleben...",
tellAdmin = "Informiere ein Teammitglied über diese Fehler: %s",
mapAdd = "Du hast eine Mapszene hinzugefügt.",
mapDel = "du hast %d Mapszene(n) entfernt.",
mapRepeat = "Füge nun den zweiten Punkt hinzu.",
scoreboard = "Spieleübersicht",
ping = "Ping: %d",
viewProfile = "Steamprofil anschauen",
copySteamID = "Kopiere Steam ID",
money = "Credits",
moneyLeft = "Deine Credits: ",
currentMoney = "Verbleibende Credits: ",
invalidClass = "Das ist keine gültige Klasse!",
invalidClassFaction = "Das ist keine gültige Klasse für deine Fraktion!",
miscellaneous = "Sonstiges",
general = "Allgemein",
observer = "Beobachter",
performance = "Leistung",
thirdperson = "Third Person",
date = "Datum",
interaction = "Interaktion",
server = "Server",
resetDefault = "Standarteinstellung wiederherstellen",
resetDefaultDescription = "\"%s\" wird auf den Standartwert \"%s\" zurückgesetzt.",
optOpenBags = "Öffne Tasche mit Inventar",
optdOpenBags = "Öffnet alle Taschen in deinem Inventar automatisch",
optShowIntro = "Intro beim Verbinden anzeigen",
optdShowIntro = "Zeigt die Helix Einführen beim nächsten Verbinden. Wird nach dem ersten Schauen automatisch deaktiviert.",
optCheapBlur = "Deaktiviere Blur",
optdCheapBlur = "Ersetzt UI Blur mit einfachem Verdunkeln.",
optObserverTeleportBack = "Kehre zur vorherigen Position zurück.",
optdObserverTeleportBack = "Kehre zum Ort zurück an dem du in den Beobachtungsmodus gegangen bist.",
optObserverESP = "Zeige Admin ESP",
optdObserverESP = "Zeige Namen und Position aller Spieler auf dem Server.",
opt24hourTime = "Nutze 24-Stundenformat",
optd24hourTime = "Zeige Zeitstempel in 24-Stundenformat, statt 12-Stundenformat (AM/PM).",
optChatNotices = "Zeige Hinweise im Chat",
optdChatNotices = "Zeige alle Hinweise Rechts-Oben stattdessen im Chat an.",
optChatTimestamps = "Zeige Zeitstempel im Chat.",
optdChatTimestamps = "Zeigt bei jeder Nachricht im Chat die Zeit an.",
optAlwaysShowBars = "Zeige Statusbalken dauerhaft.",
optdAlwaysShowBars = "Zeige Statusbalken Links-Oben dauerhaft.",
optAltLower = "Verstecke Hände wenn runtergenommen.", -- @todo remove me
optdAltLower = "Versteckt Händer wenn sie an deinem Körper herunterhängen.", -- @todo remove me
optThirdpersonEnabled = "Aktiviere Third-Person",
optdThirdpersonEnabled = "Bewegt die Kamera hinter dich. Kann auch mit dem \"ix_togglethirdperson\" Console-Command umgeschaltet werden.",
optThirdpersonClassic = "Aktiviere die klassische Third-Person Kamera",
optdThirdpersonClassic = "Bewegt die Sicht deines Charakters mit der Maus.",
optThirdpersonVertical = "Kamera Höhe",
optdThirdpersonVertical = "Wie hoch die Kamera sein soll.",
optThirdpersonHorizontal = "Kamera Offset",
optdThirdpersonHorizontal = "Wie weit links oder rechts die Kamera sein soll.",
optThirdpersonDistance = "Kamera Entfernung",
optdThirdpersonDistance = "Wie weit entfernt die Kamera sein soll.",
optThirdpersonCrouchOffset = "Camera Höhe beim Kriechen",
optdThirdpersonCrouchOffset = "Wie hoch die Kamerahöhe beim Kriechen sein soll.",
optDisableAnimations = "Deaktiviere Animationen",
optdDisableAnimations = "Verhindert Animationen für einen sofortigen Übergang.",
optAnimationScale = "Animation Scale",
optdAnimationScale = "Wie schnell Animationen laufen sollen.",
optLanguage = "Sprache",
optdLanguage = "Die Sprache der Helix UI.",
optMinimalTooltips = "Minimale HUD Tips",
optdMinimalTooltips = "Ändert die HUD Tips um weniger auffällig zu sein.",
optNoticeDuration = "Hinweiszeit",
optdNoticeDuration = "Wie lange Hinweise angezeigt werden sollen (in Sekunden).",
optNoticeMax = "Maximale Hinweise",
optdNoticeMax = "Die maximal gleichzeitig angezeigten Hinweise.",
optChatFontScale = "Chat Schiftgröße",
optdChatFontScale = "Die Größe der Schirft im Chat.",
optChatOutline = "Chat Schirftrand",
optdChatOutline = "Größe des Randes um die Schirft im Text.",
cmdRoll = "Würfelt eine Zahl zwischen 0 und der angegebenen Zahl.",
cmdPM = "Sendet eine Privatnachricht.",
cmdReply = "Sendet eine private Antwort zur letzten Person die dich angeschrieben hat.",
cmdSetVoicemail = "Setzt oder entfernt die Automatische Nachricht wenn dir jemand eine Privatnachricht sendet.",
cmdCharGiveFlag = "Gibt einer Person die angegebenen Berechtigungen.",
cmdCharTakeFlag = "Entfernt einer Person die angegebenen Berechtigungen.",
cmdToggleRaise = "Hebt oder Senkt die Waffe die du hälst.",
cmdCharSetModel = "Setzt das Model einer Person.",
cmdCharSetSkin = "Setzt den Skin des Models einer Person.",
cmdCharSetBodygroup = "Setzt die Bodygroup des Models einer Person.",
cmdCharSetAttribute = "Setzt das Level des angegebenen Attributs einer Person.",
cmdCharAddAttribute = "Fügt ein Level zum angegebenen Attirbuts einer Person hinzu.",
cmdCharSetName = "Ändert den Namen einer Person.",
cmdCharGiveItem = "Gibt das angegebenen Item einer Person.",
cmdCharKick = "Wirft jemanden aus seinem Charakter heraus.",
cmdCharBan = "Bannt einen bestimmten Charakter.",
cmdCharUnban = "Entbannt einen bestimmten Charakter.",
cmdGiveMoney = "Gibt dem Spieler den du anschaust eine angegebene Menge Geld.",
cmdCharSetMoney = "Setzt das Geld eines Spielers.",
cmdDropMoney = "Lässt Geld in einer Box vor dir fallen.",
cmdPlyWhitelist = "Whitelistet eine Person zu einer Fraktion.",
cmdCharGetUp = "Lässt deine Charakter versuchen wieder aufzustehen.",
cmdPlyUnwhitelist = "Entwhitelisted einen Charakter von eine Fraktion.",
cmdCharFallOver = "Lässt dich ohnmächtig werden.",
cmdBecomeClass = "Versucht die gegebene Klasse in deiner Fraktion zu werden.",
cmdCharDesc = "Setzt eine Beschreibung deine Körpers.",
cmdCharDescTitle = "Körperbeschreibung",
cmdCharDescDescription = "Geben deine Körperbeschreibung ein.",
cmdPlyTransfer = "Transferiert einen Spieler zu gegebenen Position.",
cmdCharSetClass = "Setzt jemanden zu einer Klasse.",
cmdMapRestart = "Führt nach der angegebenen Zeit einen Maprestart aus.",
cmdPanelAdd = "Platziert eine Webpanel.",
cmdPanelRemove = "Entfernt das Webpanel auf das du schaust.",
cmdTextAdd = "Platziert einen Text in die Welt.",
cmdTextRemove = "Entfernt den Text auf den du schaust.",
cmdMapSceneAdd = "Platziert eine Kamera für eine Mapszene.",
cmdMapSceneRemove = "Entfernt eine Kamera für eine Mapszene.",
cmdSpawnAdd = "Fügt einen Spawnpoint für die Fraktion hinzu.",
cmdSpawnRemove = "Entfernt alle Spawnpoints in deinem Sichtfeld.",
cmdAct = "Führt die %s Animation aus.",
cmdContainerSetPassword = "Setzt das Passwort des Behälters auf den du schaust.",
cmdDoorSell = "Verkauft die tür auf die du schaust.",
cmdDoorBuy = "Kauft die tür auf die du schaust.",
cmdDoorSetUnownable = "Macht die Tür auf die du schaust nicht-besitzbar.",
cmdDoorSetOwnable = "Macht die Tür auf die du schaust wieder beitzbar.",
cmdDoorSetFaction = "Setzt die Tür auf die du schaust als Besitzt einer Fraktion",
cmdDoorSetDisabled = "Deaktiviert die Tür auf die du schaust.",
cmdDoorSetTitle = "Setzt den Namen der Tür auf die du schaust.",
cmdDoorSetParent = "Setzt den Parent der Tür auf die du schaust.",
cmdDoorSetChild = "Setzt das Child ein Paar von Türen.",
cmdDoorRemoveChild = "Entfernt das Child ein Paar von Türen.",
cmdDoorSetHidden = "Versteckt die Beschreibung der Tür auf die du schaust.",
cmdDoorSetClass = "Setzt die Tür auf die du schaust als Besitz einer bestimmten Klasse.",
cmdMe = "Führe eine lokale Aktion aus.",
cmdIt = "Führe eine lokale Aktion für ein Objekt in deiner Nähe aus.",
cmdW = "Flüstere den Personen in deiner Nähe etwas.",
cmdY = "Rufe den Personen um dich etwas zu.",
cmdEvent = "Führe eine globale Aktion aus.",
cmdOOC = "Sendet eine Nachricht in den globalen Out-of-Character Chat.",
cmdLOOC = "Sendet eine Nachricht in den lokalen Out-of-Character Chat.",
}
================================================
FILE: gamemode/languages/sh_korean.lua
================================================
NAME = "한국어"
LANGUAGE = {
loading = "불러오는 중",
dbError = "DB 서버 연결 실패",
unknown = "알 수 없음",
noDesc = "정보가 존재하지 않습니다.",
create = "생성",
createTip = "새로운 캐릭터를 생성합니다.",
load = "계속",
loadTip = "플레이할 캐릭터를 불러옵니다.",
leave = "종료",
leaveTip = "서버에서 퇴장합니다.",
["return"] = "뒤로",
returnTip = "이전 메뉴로 돌아갑니다.",
name = "이름",
description = "정보",
model = "외관",
attributes = "능력",
charCreateTip = "빈칸들을 채우고 아래 '완료' 버튼을 눌러 캐릭터를 생성하십시오.",
invalid = "다음 정보가 존재하지 않습니다: %s",
descMinLen = "정보는 적어도 %d 자 이상이어야 합니다.",
player = "플레이어",
finish = "완료",
finishTip = "캐릭터 생성을 완료합니다.",
needModel = "올바른 외관을 선택하여야 합니다.",
creating = "캐릭터를 생성중입니다...",
unknownError = "오류가 발생하였습니다.",
delConfirm = "%s 영구히 완전히 삭제합니까?",
no = "아니오",
yes = "예",
itemInfo = "이름: %s\n정보: %s",
cloud_no_repo = "클라우드 경로가 존재하지 않습니다.",
cloud_no_plugin = "클라우드 추가 기능이 존재하지 않습니다.",
inv = "인벤토리",
plugins = "추가 기능",
author = "제작자",
version = "버전",
characters = "캐릭터",
business = "사업",
settings = "설정",
config = "설정",
chat = "대화",
appearance = "외관",
misc = "기타",
oocDelay = "%s 초를 더 기다려야 OOC 대화가 가능합니다.",
loocDelay = "%s 초를 더 기다려야 LOOC 대화가 가능합니다.",
usingChar = "이미 이 캐릭터로 서버에서 플레이하고 있습니다.",
notAllowed = "당신은 이것을 할 권한이 없습니다.",
itemNoExist = "당신이 요청한 아이템은 존재하지 않습니다.",
cmdNoExist = "당신이 요청한 명령은 존재하지 않습니다.",
plyNoExist = "그 이름을 가진 플레이어를 검색할 수 없습니다.",
cfgSet = "%s 님이 \"%s\" 를 %s 으로 설정하였습니다.",
drop = "버리기",
dropTip = "아이템을 소지품에서 제외시킵니다.",
take = "가지기",
takeTip = "아이템을 소지품에 추가시킵니다.",
dTitle = "소유되지 않은 문",
dTitleOwned = "소유된 문",
dIsNotOwnable = "이 문은 소유할 수 없습니다.",
dIsOwnable = "F2를 눌러서 이 문을 소유할 수 있습니다.",
dMadeUnownable = "당신은 이 문을 소유할 수 없도록 설정했습니다.",
dMadeOwnable = "당신은 이 문을을 소유할 수 있도록 설정했습니다.",
dNotAllowedToOwn = "이 문을 소유하도록 허가되지 않았습니다.",
dSetDisabled = "당신은 이 문의 기능을 껐습니다.",
dSetNotDisabled = "당신은 이 문의 기능을 다시 켰습니다.",
dSetHidden = "당신은 이 문을 숨겼습니다.",
dSetNotHidden = "당신은 이 문을 숨김 해제했습니다.",
dSetParentDoor = "당신은 이 문을 상위 개체로 설정하였습니다.",
dCanNotSetAsChild = "당신은 이 문을 하위 개체로 설정할 수 없습니다.",
dAddChildDoor = "당신은 이 문을 하위 개체로 설정하였습니다.",
dRemoveChildren = "당신은 이 문에 할당된 모든 하위 개체를 삭제했습니다.",
dRemoveChildDoor = "당신은 이 문을 하위 개체에서 삭제했습니다.",
dNoParentDoor = "상위 개체인 문이 없습니다.",
dOwnedBy = "이 문은 %s 님의 소유입니다.",
dConfigName = "문",
dSetFaction = "이 문은 이제 %s 단체에 속하게 됩니다.",
dRemoveFaction = "이 문은 이제 어느 단체에도 속하지 않습니다.",
dNotValid = "유효한 문을 바라보고 있어야 합니다.",
canNotAfford = "이 문을 구매할 충분한 자금을 가지고 있지 않습니다.",
dPurchased = "이 문을 %s으로 구매했습니다.",
dSold = "당신은 이 문을 %s으로 판매했습니다.",
notOwner = "당신은 이 문을 소유하고 있지 않습니다.",
invalidArg = "#%s 번째 명령 변수에 올바른 값을 입력해야 합니다.",
invalidFaction = "제시된 이름으로 된 단체를 찾을 수 없습니다.",
flagGive = "%s 님이 %s 님에게 '%s' 권한을 주었습니다.",
flagGiveTitle = "권한 주기",
flagGiveDesc = "이 권한들을 플레이어에게 줍니다.",
flagTake = "%s 님이 '%s' 권한을 %s 님으로 부터 받았습니다.",
flagTakeTitle = "플래그 가지기.",
flagTakeDesc = "이 권한들을 플레이어에게서 뺏습니다.",
flagNoMatch = "이 행동은 \"%s\" 권한을 필요로 합니다.",
textAdded = "텍스트를 추가하였습니다.",
textRemoved = "%s개의 택스트를 삭제하였습니다.",
moneyTaken = "%s 발견.",
businessPurchase = "당신은 %s 을/를 %s에 구매하였습니다.",
businessSell = "당신은 %s 을/를 %s에 판매했습니다.",
cChangeModel = "%s님이 %s님의 외관을 교체했습니다: %s.",
cChangeName = "%s님이 %s님의 이름을 교체했습니다: %s.",
cChangeSkin = "%s 가 %s's 의 스킨을 %s 로 바꾸었습니다.",
cChangeGroups = "%s 가 %s 의 \"%s\" 바디그룹을 %s 로 바꾸었습니다.",
cChangeFaction = "%s 는 %s 를 %s 팩션으로 이동시켰습니다.",
playerCharBelonging = "이 물건은 당신의 다른 캐릭터의 물건입니다.",
spawnAdd = "%s 개의 시작지점을 추가하였습니다.",
spawnDeleted = "%s개의 시작지점을 삭제하였습니다.",
someone = "누군가",
rgnLookingAt = "당신이 보고 있는 사람이 당신을 인식하도록 선언.",
rgnWhisper = "귓속말 거리에 있는 사람을 당신을 인식하도록 선언",
rgnTalk = "일반 대화 거리에 있는 사람을 당신을 인식하도록 선언",
rgnYell = "외침 대화 거리에 있는 사람을 당신을 인식하도록 선언",
icFormat = "%s: \"%s\"",
rollFormat = "%s님이 주사위를 굴렸습니다: %s.",
wFormat = "%s(귓속말): \"%s\"",
yFormat = "%s(외침): \"%s\"",
sbOptions = "%s님에 대한 선택지를 보려면 클릭하십시오.",
spawnAdded = "%s 단체를 위한 시작 지점이 추가되었습니다.",
whitelist = "%s님이 %s님을 %s 단체에 들어가도록 허가했습니다.",
unwhitelist = "%s님이 %s님을 %s 단체에 들어가는 것을 금지했습니다.",
gettingUp = "몸을 일으키는 중입니다...",
wakingUp = "정신을 차리는 중입니다...",
Weapons = "무기",
checkout = "물건 결제 (%s)",
purchase = "구매",
purchasing = "결제 진행중...",
success = "성공",
buyFailed = "결제 실패.",
buyGood = "결제가 완료되었습니다!",
shipment = "소유물",
shipmentDesc = "이 소유물은 %s님의 명의로 되어있습니다.",
class = "직업",
classes = "직업",
illegalAccess = "잘못된 접근입니다.",
becomeClassFail = "%s이/가 되는 것에 실패했습니다.",
becomeClass = "%s이/가 되었습니다.",
attributeSet = "당신은 %s님의 %s을/를 %s로 설정하였습니다.",
attributeUpdate = "당신은 %s님의 %s을/를 %s만큼 추가하였습니다.",
noFit = "소지품 공간이 부족합니다.",
help = "도움말",
commands = "명령어",
helpDefault = "목차 선택",
doorSettings = "문 설정",
sell = "판매",
access = "접근",
locking = "이 물건을 잠그는 중입니다...",
unlocking = "이 물건을 여는 중입니다...",
modelNoSeq = "당신의 외관은 이 행동을 지원하지 않습니다.",
notNow = "당신은 아직 이 행동을 할 수 없습니다.",
faceWall = "이 행동을 위해선 벽을 바라보고 있어야 합니다.",
faceWallBack = "이 행동을 위해선 벽을 등지고 있어야 합니다.",
descChanged = "당신의 캐릭터의 정보를 변경했습니다.",
charMoney = "당신의 소지금은 %s 입니다.",
charFaction = "당신은 %s 단체에 소속되어 있습니다.",
charClass = "당신은 이 단체의 %s 입니다.",
noOwner = "소유자가 존재하지 않습니다.",
invalidIndex = "아이템의 구분 번호가 올바르지 않습니다.",
invalidItem = "아이템 객체 참조가 잘못되었습니다.",
invalidInventory = "소지품 객체 참조가 잘못되었습니다.",
home = "초기",
charKick = "%s님이 %s님의 캐릭터를 추방하였습니다.",
charBan = "%s님이 %s님의 캐릭터를 영구히 추방하였습니다.",
charBanned = "이 캐릭터는 사용이 금지되었습니다.",
setMoney = "당신은 %s님의 돈을 %s으로 설정하였습니다.",
itemPriceInfo = "이 아이템을 %s에 구매가 가능합니다.\n이 아이템을 %s에 탄매가 가능합니다",
free = "무료",
vendorNoSellItems = "판매할 아이템이 없습니다.",
vendorNoBuyItems = "구매할 아이템이 없습니다.",
vendorSettings = "상인 설정",
vendorUseMoney = "상인에 제한된 돈",
vendorNoBubble = "말풍선 보이기",
mode = "상태",
price = "가격",
stock = "재고",
none = "없음",
vendorBoth = "판매와 구매",
vendorBuy = "구매 전용",
vendorSell = "판매 전용",
maxStock = "최대 재고",
vendorFaction = "팩션 에디터",
buy = "구매",
vendorWelcome = "어서오세요. 무엇을 찾으십니까?",
vendorBye = "다음에 또 오세요!",
charSearching = "이미 캐릭터를 수색하고 있습니다.",
charUnBan = "%s 님이 다음 캐릭터를 금지 해제했습니다: %s.",
charNotBanned = "이 캐릭터는 금지되지 않았습니다.",
containerPassword = "이 보관함의 암호를 %s 으로 설정하였습니다.",
containerPasswordRemove = "이 보관함의 암호를 삭제했습니다.",
containerPasswordWrite = "암호를 입력해 주십시오.",
wrongPassword = "암호가 다릅니다.",
cheapBlur = "블러 효과 사용 (FPS 향상)",
quickSettings = "빠른 설정",
vmSet = "개인 귓속말을 설정했습니다.",
vmRem = "개인 귓속말을 삭제했습니다.",
altLower = "주먹 미사용시 숨김",
noPerm = "이 행위를 할 권한이 없습니다.",
youreDead = "당신은 죽었습니다",
injMajor = "중상을 입음.",
injLittle = "부상을 입음.",
toggleESP = "어드민 월핵 사용",
chgName = "이름 변경",
chgNameDesc = "아래에 캐릭터의 새로운 이름을 입력하세요.",
thirdpersonToggle = "3인칭 사용",
thirdpersonClassic = "클래식 3인칭 사용",
equippedBag = "가방 내부에 사용중인 아이템이 있습니다.",
useTip = "이 아이템을 사용합니다.",
equipTip = "이 아이템을 착용합니다.",
unequipTip = "이 아이템을 착용해제합니다.",
consumables = "소모품",
plyNotValid = "당신은 잘못된 플레이어를 보고있습니다.",
restricted = "당신은 저지되었습니다.",
viewProfile = "스팀 프로필 보기",
salary = "당신은 월급으로 부터 &s 만큼의 돈이 들어왔습니다.",
noRecog = "당신은 이 사람을 인식하지 않았습니다.",
curTime = "지금 시각은 %s.",
vendorEditor = "상인 수정",
edit = "수정",
disable = "해제",
vendorPriceReq = "이 물품의 새로운 가격을 적으십시오.",
vendorEditCurStock = "현재 재고 수정",
you = "당신",
vendorSellScale = "판매 가격 규모",
vendorNoTrade = "당신은 이 상인과 거래 할수없습니다.",
vendorNoMoney = "이 상인은 해당 물품을 사 들일수 없습니다.",
vendorNoStock = "이 상인은 해당 물품의 재고가 없습니다.",
contentTitle = "Helix 콘텐츠 없음.",
contentWarning = "당신은 Helix 콘텐츠가 적용되어있지 않습니다. 특정 기능이 누락될 수 있습니다.\nHelix 콘텐츠를 적용해야 합니다.",
flags = "플래그"
}
================================================
FILE: gamemode/languages/sh_norwegian.lua
================================================
NAME = "Norwegian"
LANGUAGE = {
loading = "Laster",
dbError = "Database tilkobling feilet",
unknown = "Ukjent",
noDesc = "Ingen beskrivelse tilgjengelig",
create = "Lag",
createTip = "Lag en ny karakter til å spille med.",
load = "Last",
loadTip = "Velg en tidligere brukt karakter til å spille med.",
leave = "Forlat",
leaveTip = "Forlat den nåværende serveren.",
["return"] = "Tilbake",
returnTip = "Tilbake til den forrige menyen.",
name = "Navn",
description = "Beskrivelse",
model = "Modell",
attributes = "Attributter",
charCreateTip = "Fyll inn feltene nedenfor og trykk på 'Fullfør' for å skape din karakter.",
invalid = "Du har gitt et ugyldig %s",
descMinLen = "Din beskrivelse må være minst %d bokstav(er).",
player = "Spiller",
finish = "Fullfør",
finishTip = "Fullfør med å lage karakteren.",
needModel = "Du må velge en gyldig modell",
creating = "Din karakter blir skapt...",
unknownError = "Det har oppstått en ukjent feil",
delConfirm = "Er du sikker på at du vil PERMANENT slette %s?",
no = "Nei",
yes = "Ja",
itemInfo = "Navn: %s\nDescription: %s",
cloud_no_repo = "The repository provided is not valid.",
cloud_no_plugin = "The plugin provided is not valid.",
inv = "Inventar",
plugins = "Tillegg",
author = "Forfatter",
version = "Versjon",
characters = "Karakterer",
business = "Handel",
settings = "Innstillinger",
config = "Konfigurasjon",
chat = "Chat",
appearance = "Utseende",
misc = "Diverse",
oocDelay = "Du må vente %s sekund(er) med å bruke OOC igjen.",
loocDelay = "Du må vente %s sekund(er) med å bruke LOOC igjen.",
usingChar = "Du bruker allerede denne karakteren.",
itemNoExist = "Beklager, det elementet du forespurte finnes ikke.",
cmdNoExist = "Beklager, den kommandoen ekisterer ikke.",
plyNoExist = "Beklager, en matchende spiller ble ikke funnet.",
cfgSet = "%s har satt \"%s\" til %s.",
drop = "Frigjør",
dropTip = "Frigjør dette elementet fra ditt inventar.",
take = "Ta",
takeTip = "Ta dette elementet og putt det i inventaret ditt.",
dTitle = "Ueid Dør",
dTitleOwned = "Kjøpt Dør",
dIsNotOwnable = "Denne døren er ikke mulig å kjøpe.",
dIsOwnable = "Du kan kjøpe denne døren med å trykke på F2.",
dMadeUnownable = "Du har gjordt denne døren umulig å kjøpe.",
dMadeOwnable = "Du har gjordt denne døren mulig å kjøpe.",
dNotAllowedToOwn = "Du er ikke tillatt å eie denne døra.",
dSetDisabled = "Du har deaktivert denne døren.",
dSetNotDisabled = "Du har gjordt denne døren ikke lenger deaktivert.",
dSetHidden = "Du har gjordt denne døren gjemt.",
dSetNotHidden = "Du har gjordt at døren ikke er gjemt lenger.",
dSetParentDoor = "Du har gjort denne døren, hoveddøren.",
dCanNotSetAsChild = "Du kan ikke sette hoveddøren som sekundær døren.",
dAddChildDoor = "Du har lagt til en sekundær dør.",
dRemoveChildren = "Du har fjernet alle sekundære dører fra denne døren.",
dRemoveChildDoor = "Du har fjernet denne døren fra å være en sekundær dør.",
dNoParentDoor = "Du har ikke en hoveddør satt.",
dOwnedBy = "Denne døren er eid av %s.",
dConfigName = "Dører",
dSetFaction = "Denne døren tilhører %s gruppen.",
dRemoveFaction = "Denne døren tilhører ikke en gruppe lenger.",
dNotValid = "Du ser ikke på en gyldig dør.",
canNotAfford = "Du har ikke råd til å kjøpe dette.",
dPurchased = "Du har kjøpt denne døren for %s.",
dSold = "Du har solgt denne døren for %s.",
notOwner = "Du er ikke eieren av dette.",
invalidArg = "Du har gitt en ugyldig verdi for argumentet #%s.",
flagGive = "%s har gitt %s '%s' flaggene.",
flagGiveTitle = "Gi Flagg",
flagGiveDesc = "Gi de følgene flaggene til en spiller.",
flagTake = "%s har tatt '%s' flaggene fra %s.",
flagTakeTitle = "Ta Flagg",
flagTakeDesc = "Fjern de følgene flaggene til en spiller.",
flagNoMatch = "Du må ha \"%s\" Flagg for å gjøre denne handlingen.",
textAdded = "Du har lagt til en tekst.",
textRemoved = "Du har fjernet %s tekst(er).",
moneyTaken = "Du har funnet %s.",
businessPurchase = "Du har kjøpt %s for %s.",
businessSell = "Du har solgt %s for %s.",
cChangeModel = "%s endret %s's modell til %s.",
cChangeName = "%s endret %s's navn til %s.",
cChangeSkin = "%s endret %s's skin til %s.",
cChangeGroups = "%s endret %s's \"%s\" kroppsgruppe to %s.",
cChangeFaction = "%s har overført %s til %s gruppen.",
playerCharBelonging = "Dette objektet tilhører en av dine andre karakterer.",
invalidFaction = "Du har gitt en ugyldig gruppe.",
spawnAdd = "Du har lagt til spawnen for %s.",
spawnDeleted = "Du har fjernet %s spawn punkt(er).",
someone = "Noen",
rgnLookingAt = "Tillat personen du ser på å gjenkjenne deg.",
rgnWhisper = "Tillat de innen hviske radius å gjenkjenne deg.",
rgnTalk = "Tillat de innen prate radius å gjenkjenne deg.",
rgnYell = "Tillat de innen rope radius å gjenkjenne deg.",
icFormat = "%s sier \"%s\"",
rollFormat = "%s har rullet %s.",
wFormat = "%s hvisker \"%s\"",
yFormat = "%s roper \"%s\"",
sbOptions = "Klikk for å se instillingene for %s.",
spawnAdded = "Du har lagt til spawnen for %s.",
whitelist = "%s har hvitelistet %s for %s gruppen.",
unwhitelist = "%s har fjernet %s fra hvitelisten til %s gruppen.",
gettingUp = "Du reiser deg opp...",
wakingUp = "Du er kommer til bevissthet...",
Weapons = "Våpen",
checkout = "Gå til kassen (%s)",
purchase = "Kjøp",
purchasing = "Kjøp = er...",
success = "Suksess",
buyFailed = "Kjøpet mislyktes.",
buyGood = "Kjøp vellykket!",
shipment = "Forsendelsen",
shipmentDesc = "Denne forsendelsen tilhører %s.",
class = "Klasse",
classes = "Klasser",
illegalAccess = "Ulovlig Tilgang.",
becomeClassFail = "Klarte ikke å bli %s.",
becomeClass = "Du har bltt %s.",
attributeSet = "Du har satt %s's %s til %s.",
attributeUpdate = "Du har lagt til %s's %s av %s.",
noFit = "Dette elementet kan ikke passe i inventaret ditt.",
help = "Hjelp",
commands = "Kommandoer",
helpDefault = "Velg et katagori",
doorSettings = "Dør innstillinger",
sell = "Selg",
access = "Tilgang",
locking = "Låser denne enheten...",
unlocking = "Låser opp denne enheten...",
modelNoSeq = "Din modell støtter ikke denne handlingen.",
notNow = "Du er ikke tillatt.",
faceWall = "Du må stå mot veggen for å gjøre dette.",
faceWallBack = "Din rygg må stå mot veggen for å gjøre dette.",
descChanged = "Du har endret din karakters beskrivelse.",
charMoney = "Du har akkurat nå %s.",
charFaction = "Du er et medlem av denne %s gruppen.",
charClass = "Du er %s i gruppen.",
noOwner = "Eieren er ugyldig.",
notAllowed = "Denne handlingen er ikke tillatt.",
invalidIndex = "Elementet's Index er ugyldig.",
invalidItem = "Element Objektet er ugyldig.",
invalidInventory = "Inventar objektet er ugyldig.",
home = "Hjem",
charKick = "%s sparket karakteren %s.",
charBan = "%s utestengte karakteren %s.",
charBanned = "Denne karakteren er utestengt.",
setMoney = "Du har satt %s's penger til %s.",
itemPriceInfo = "Du kan kjøpe dette elementet for %s.\nDu kan kjøpe dette elementet for %s",
free = "Gratis",
vendorNoSellItems = "Det er ingen elementer for å selle.",
vendorNoBuyItems = "Det er ingen elementer til å kjøpe.",
vendorSettings = "Leverandør Innstillinger",
vendorUseMoney = "Leverandør skal bruke penger?",
vendorNoBubble = "Gjem leverandør bobblen?",
mode = "Modus",
price = "Pris",
stock = "På lager",
none = "Ingen",
vendorBoth = "Kjøp og selg",
vendorBuy = "Kun Kjøp",
vendorSell = "Kun Selg",
maxStock = "Maks på lager",
vendorFaction = "Gruppe endrer",
buy = "Kjøp",
vendorWelcome = "Velkommen til butikken min, hva kan jeg gi deg i dag?",
vendorBye = "Kom tilbake snart!",
charSearching = "Du søker allerede etter en annen karakter, vennligst vent.",
charUnBan = "%s har fjernet %s fra karakter utestengt listen.",
charNotBanned = "Denne karakteren er ikke utestengt.",
containerPassword = "Du har satt dette lagerets passord til %s.",
containerPasswordRemove = "Du har fjernet dette lagerets passord.",
containerPasswordWrite = "Skriv inn passordet.",
wrongPassword = "Du har skrivd inn feil passord.",
cheapBlur = "Deaktiver uklarhet? (Bedre FPS)",
quickSettings = "Hurtiginnstillinger",
vmSet = "Du har satt ditt mobilsvar.",
vmRem = "Du har fjernet ditt mobilsvar.",
altLower = "Gjemme hendene når senket?",
noPerm = "Du er ikke tillatt til å gjøre dette.",
youreDead = "Du er død.",
injMajor = "Ser kritisk skadd ut.",
injLittle = "Ser skadd ut.",
toggleESP = "Veksle Admin ESP",
chgName = "Endre navn",
chgNameDesc = "Skriv in karakterens nye navn under.",
thirdpersonToggle = "Veksle Tredje-Person",
thirdpersonClassic = "Bruk klassisk Tredje-Person",
equippedBag = "Posen at du flyttet har utstyrt element.",
useTip = "Bruk dette elementet.",
equipTip = "Ta på dette elementet.",
unequipTip = "Ta av dette elementet.",
consumables = "Forbruksvarer",
plyNotValid = "Du ser ikke på en gyldig spiller.",
restricted = "Du har blitt tilbakeholdne.",
viewProfile = "Se Steam Profil",
salary = "Du har motatt %s fra lønnen din.",
noRecog = "Du kjenner ikke denne personen.",
curTime = "Tiden er %s.",
vendorEditor = "Leverandør Redigering",
edit = "Rediger",
disable = "Deaktiver",
vendorPriceReq = "Skriv in den nye prisen for dette elementet.",
vendorEditCurStock = "Rediger nåværende lager",
you = "Du",
vendorSellScale = "Utsalgspris skala",
vendorNoTrade = "Du er ikke i stand til å handle med denne leverandøren.",
vendorNoMoney = "Denne leverandøren ikke har råd til dette elementet.",
vendorNoStock = "Denne leverandøren har ikke at varen på lager.",
contentTitle = "Helix Innhold Mangler",
contentWarning = "Du har ikke Helix innhold montert. Dette kan føre til enkelte funksjoner mangler. \nVil du åpne Workshop side for Helix innhold?",
flags = "Flagg"
}
================================================
FILE: gamemode/languages/sh_polish.lua
================================================
-- Autorzy: zgredinzyyy (Poprawki + Brakujące rzeczy) || Veran120, Michał, Lechu2375 https://github.com/lechu2375/helix-polishlocalization/blob/master/sh_polish.lua
NAME = "Polski"
LANGUAGE = {
helix = "Helix",
introTextOne = "fist industries prezentuje",
introTextTwo = "w współpracy z %s",
introContinue = "wciśnij spację by kontynuować",
helpIdle = "Wybierz kategorię",
helpCommands = "Komendy z parametrami w są wymagane, a w [nawiasach kwadratowych] są opcjonalne",
helpFlags = "Flagi z zielonym tłem są dostępne przez tą postać.",
creditSpecial = "Podziękowania dla",
creditLeadDeveloper = "Główny Developer",
creditUIDesigner = "UI Designer",
creditManager = "Project Manager",
creditTester = "Lead Tester",
chatTyping = "Pisze...",
chatTalking = "Mówi...",
chatYelling = "Krzyczy...",
chatWhispering = "Szepcze...",
chatPerforming = "Wykonuje...",
chatNewTab = "Nowa karta",
chatReset = "Zresetuj pozycję",
chatResetTabs = "Resetuj karty",
chatCustomize = "Personalizuj...",
chatCloseTab = "Zamknij kartę",
chatTabName = "Nazwa karty",
chatAllowedClasses = "Dostępne Klasy czatów",
chatTabExists = "Karta z taką nazwą już instnieje!",
chatMarkRead = "Odznacz wszystko jako przeczytane",
community = "Community",
checkAll = "Zaznacz wszystko",
uncheckAll = "Odznacz wszystko",
color = "Kolor",
type = "Typ",
display = "Wyświetlanie",
loading = "Ładowanie",
dbError = "Połączenie z bazą danych nie powiodło się",
unknown = "Nieznane",
noDesc = "Opis niedostępny",
create = "Stwórz",
update = "Zaktualizuj",
load = "Załaduj postać",
loadTitle = "Załaduj swoją postać",
leave = "Opuść serwer",
leaveTip = "Opuść ten serwer.",
["return"] = "Powrót",
returnTip = "Powróć do poprzedniego menu.",
proceed = "Kontynuuj",
faction = "Frakcja",
skills = "Umiejętności",
choose = "Wybierz",
chooseFaction = "Wybierz Frakcje",
chooseDescription = "Zdefiniuj swoją postać",
chooseSkills = "Dostosuj swoje umiejętności",
name = "Imię i Nazwisko",
description = "Opis",
model = "Model",
attributes = "Atrybuty",
attribPointsLeft = "Pozostałe punkty",
charInfo = "Informacje o postaci",
charCreated = "Udało ci się stworzyć swoją postać.",
charCreateTip = "Wypełnij pola poniżej i klinij 'Zakończ' aby stworzyć swoją postać.",
invalid = "Podałeś niewłaściwe(ą) %s",
nameMinLen = "Twoje imię musi zawierać conajmniej %d znaków!",
nameMaxLen = "Twoje imię nie może posiadać więcej niż %d znaków!",
descMinLen = "Twoje opis musi zawierać conajmniej %d znaków!",
maxCharacters = "Nie możesz utworzyć więcej postaci!",
player = "Gracz",
finish = "Zakończ",
finishTip = "Ukończ tworzenie postaci.",
needModel = "Musisz wybrać poprawny model!",
creating = "Twoja postać jest aktualnie tworzona...",
unknownError = "Wystąpił nieznany błąd!",
areYouSure = "jesteś pewien?",
delete = "Usuń",
deleteConfirm = "Ta postać będzie bezpowrotnie usunięta!",
deleteComplete = "%s został(a) usunięty(a).",
no = "Nie",
yes = "Tak",
close = "Zamknij",
save = "Zapisz",
itemInfo = "Imię Nazwisko: %s\nOpis: %s",
itemCreated = "Przedmiot(y) pomyślnie utworzony.",
cloud_no_repo = "Repozytorium, które zostało podane jest nieprawidłowe.",
cloud_no_plugin = "Podany plugin jest nieprawidłowy.",
inv = "Ekwipunek",
plugins = "Pluginy",
pluginLoaded = "%s włączył \"%s\" plugin na następne załadowanie mapy.",
pluginUnloaded = "%s wyłączył \"%s\" plugin z następnego załadowania mapy.",
loadedPlugins = "Załadowane Pluginy",
unloadedPlugins = "Niezaładowane Pluginy",
on = "On",
off = "Off",
author = "Autor",
version = "Wersja",
characters = "Postacie",
business = "Biznes",
settings = "Opcje",
config = "Konfiguracja",
chat = "Czat",
appearance = "Wygląd",
misc = "Różne",
oocDelay = "Musisz poczekać %s sekund przed ponownym użyciem OOC.",
loocDelay = "Musisz poczekać %s sekund przed ponownym użyciem LOOC.",
usingChar = "Aktualnie używasz tej postaci.",
notAllowed = "Przepraszamy, nie masz uprawnień do zrobienia tego.",
itemNoExist = "Przedmiot o który prosiłeś nie istnieje.",
cmdNoExist = "Taka komenda nie istnieje.",
charNoExist = "Nie znaleziono pasującej postaci.",
plyNoExist = "Nie znaleziono pasującego gracza.",
cfgSet = "%s ustawił \"%s\" na %s.",
drop = "Wyrzuć",
dropTip = "Upuszcza ten przedmiot z twojego ekwipunku.",
take = "Podnieś",
takeTip = "Weź ten przedmiot i umieść go w swoim ekwipunku.",
dTitle = "Drzwi do kupienia",
dTitleOwned = "Wykupione Drzwi",
dIsNotOwnable = "Tych drzwi nie można kupić.",
dIsOwnable = "Możesz kupić te drzwi naciskając F2.",
dMadeUnownable = "Uczyniłeś te drzwi niemożliwymi do kupienia.",
dMadeOwnable = "Uczyniłeś te drzwi możliwymi do kupienia.",
dNotAllowedToOwn = "Nie możesz kupić tych drzwi.",
dSetDisabled = "Wyłączyłeś te drzwi z użytku.",
dSetNotDisabled = "Ponownie można używać tych drzwi.",
dSetHidden = "Schowałeś te drzwi.",
dSetNotHidden = "Usunąłeś te drzwi z ukrytych.",
dSetParentDoor = "Uczyniłeś te drzwi swoimi drzwiami nadrzędnymi.",
dCanNotSetAsChild = "Nie możesz ustawi aby drzwi nadrzędne były drzwiami podrzędnymi.",
dAddChildDoor = "You have added a this door as a child.",
dRemoveChildren = "Usunąłeś wszystkie drzwi podrzędne należące do tych drzwi.",
dRemoveChildDoor = "Te drzwi już nie są drzwiami podrzędnymi.",
dNoParentDoor = "Nie masz ustawionych drzwi nadrzędnych.",
dOwnedBy = "Te drzwi należą do %s.",
dConfigName = "Drzwi",
dSetFaction = "Te drzwi należą teraz do frakcji %s.",
dRemoveFaction = "Te drzwi już nie należą do żadnej frakcji.",
dNotValid = "Nie patrzysz na prawidłowe drzwi.",
canNotAfford = "Nie stać Cię na kupienie tego.",
dPurchased = "Kupiłeś te drzwi za %s.",
dSold = "Sprzedałeś te drzwi za %s.",
notOwner = "Nie jesteś właścicielem tego.",
invalidArg = "Podałeś niepoprawną wartość dla argumentu #%s.",
invalidFaction = "Frakcja, którą podałeś nie została znaleziona!",
flagGive = "%s dał %s następujące flagi: '%s'.",
flagGiveTitle = "Daj Flagi",
flagTake = "%s zabrał od %s następujące flagi: '%s'.",
flagTakeTitle = "Zabierz Flagi",
flagNoMatch = "Musisz posiadać flagę(i) \"%s\" aby wykonać tą czynność.",
panelAdded = "Dodałeś nowy panel.",
panelRemoved = "Usunąłęś %d panel(e)",
textAdded = "Dodałeś tekst.",
textRemoved = "Usunąłeś %s tekst(y).",
moneyTaken = "Znalazłeś %s.",
moneyGiven = "Otrzymałeś %s.",
insufficientMoney = "Nie posiadasz tyle środków!",
businessPurchase = "Kupiłeś %s za %s.",
businessSell = "Sprzedałeś %s za %s.",
businessTooFast = "Zaczekaj przed następnym kupnem!",
cChangeModel = "%s zmienił model gracza %s na %s.",
cChangeName = "%s zmienił imię gracza %s na %s.",
cChangeSkin = "%s zmienił model %s na %s.",
cChangeGroups = "%s zmienił bodygroupy %s \"%s\" na %s.",
cChangeFaction = "%s przeniósł %s do frakcji %s.",
playerCharBelonging = "Ten przedmiot należy do innej postaci należącej do Ciebie.",
spawnAdd = "Dodałeś punkt odradzania dla %s.",
spawnDeleted = "Usunąłeś %s punkt(y) odradzania się.",
someone = "Ktoś",
rgnLookingAt = "Pozwól osobie na którą patrzysz, aby Cię rozpoznawała.",
rgnWhisper = "Pozwól tym, którzy są w zasięgu Twoich szeptów, aby Cię rozpoznawali.",
rgnTalk = "Pozwól tym, którzy są w zasięgu normalnych rozmów, aby Cię rozpoznawali.",
rgnYell = "Pozwól tym, którzy są w zasięgu Twoich krzyków, aby Cię rozpoznawali.",
icFormat = "%s mówi: \"%s\"",
rollFormat = "%s wylosował %s.",
wFormat = "%s szepcze: \"%s\"",
yFormat = "%s krzyczy: \"%s\"",
sbOptions = "Kliknij aby zobaczyć opcje dla %s.",
spawnAdded = "Dodałeś punkt odradzania dla %s.",
whitelist = "%s dodał %s na whitelistę frakcji %s.",
unwhitelist = "%s usunął %s z whitelisty frakcji %s.",
noWhitelist = "Nie masz dostępu do whitelisty na tą postać!",
gettingUp = "Podnosisz się...",
wakingUp = "Wraca Ci świadomość...",
Weapons = "Broń",
checkout = "Idź do kasy (%s)",
purchase = "Kup",
purchasing = "Kupuję...",
success = "Sukces",
buyFailed = "Zakupy nie powiodły się.",
buyGood = "Zakupy udane!",
shipment = "Dostawa",
shipmentDesc = "Ta dostawa należy do %s.",
class = "Klasa",
classes = "Klasy",
illegalAccess = "Nielegalny Dostęp.",
becomeClassFail = "Nie udało Ci się zostać %s.",
becomeClass = "Zostałeś %s.",
setClass = "Ustawiłeś klasę %s na %s.",
attributeSet = "Ustawiłeś %s %s na %s.",
attributeNotFound = "You have specified an invalid attribute!",
attributeUpdate = "Podwyższyłeś %s %s o %s.",
noFit = "Nieposiadasz wystarczająco miejsca, aby zmieścić ten przedmiot!",
itemOwned = "Nie możesz wchodzić w interakcje z przedmiotami, które posiadasz jako inna postać!",
help = "Pomoc",
commands = "Komendy",
doorSettings = "Ustawienia Drzwi",
sell = "Sprzedaj",
access = "Dostęp",
locking = "Blokowanie tego przedmiotu...",
unlocking = "Odblokowywanie tego przedmiotu...",
modelNoSeq = "Twój model nie obsługuje tej animacji.",
notNow = "Nie możesz tego aktualnie zrobić.",
faceWall = "Musisz patrzeć na ścianę aby to wykonać.",
faceWallBack = "Musisz stać tyłem do ściany aby to wykonać.",
descChanged = "Zmieniłeś rysopis swojej postaci.",
noOwner = "Nieprawidłowy właściciel.",
invalidItem = "Wskazałeś nieprawidłowy przedmiot!",
invalidInventory = "Wskazałeś nieprawidłowy ekwipunek!",
home = "Strona Główna",
charKick = "%s wyrzucił %s.",
charBan = "%s zablokował postać %s.",
charBanned = "Ta postać jest zablokowana.",
charBannedTemp = "Ta postać jest tymczasowo zablokowana.",
playerConnected = "%s połączył się z serwerem.",
playerDisconnected = "%s wyszedł z serwera.",
setMoney = "Ustawiłeś ilość pieniędzy %s na %s.",
itemPriceInfo = "Możesz kupić ten przedmiot za %s.\nMożesz sprzedać ten przedmiot za %s",
free = "Darmowe",
vendorNoSellItems = "Nie ma przedmiotów do sprzedania.",
vendorNoBuyItems = "Nie ma przedmiotów do kupienia.",
vendorSettings = "Ustawienia sprzedawców",
vendorUseMoney = "Czy sprzedawcy powinni używać pieniędzy?",
vendorNoBubble = "Ukryć dymek sprzedawcy?",
mode = "Tryb",
price = "Cena",
stock = "Zasób",
none = "Nic",
vendorBoth = "Kupowanie i Sprzedawanie",
vendorBuy = "Tylko kupowanie",
vendorSell = "Tylko sprzedawanie",
maxStock = "Maksymalny zasób",
vendorFaction = "Edytor frakcji",
buy = "Kup",
vendorWelcome = "Witaj w moim sklepie, czy mogę Ci coś podać?",
vendorBye = "Przyjdź niedługo z powrotem!",
charSearching = "Aktualnie szukasz już innej postaci, proszę poczekać.",
charUnBan = "%s odblokował postać %s.",
charNotBanned = "Ta postać nie jest zablokowana.",
quickSettings = "Szybkie Ustawienia",
vmSet = "Ustawiłeś swoją pocztę głosową.",
vmRem = "Usunąłęś swoją pocztę głosową.",
noPerm = "Nie możesz tego zrobić!",
youreDead = "Jesteś martwy",
injMajor = "Widoczne krytyczne obrażenia.",
injLittle = "Widoczne obrażenia.",
chgName = "Zmień Imię i Nazwisko.",
chgNameDesc = "Wprowadź nowę imię i nazwisko postaci poniżej.",
weaponSlotFilled = "Nie możesz użyć kolejnej broni typu %s!",
equippedBag = "Nie możesz przemieszczać torby z wyekwipowanym przedmiotem!",
equippedWeapon = "Nie możesz przemieszczać aktualnie wyekwipowanej broni!",
nestedBags = "Nie możesz wrzucić torby do torby!",
outfitAlreadyEquipped = "Już nosisz ubranie tego typu!",
useTip = "Używa przedmiotu.",
equipTip = "Zakłada przedmiot.",
unequipTip = "Zdejmuje przedmiot.",
consumables = "Towary konsumpcyjne.",
plyNotValid = "Nie patrzysz na prawidłowego gracza.",
restricted = "Zostałeś związany.",
salary = "Otrzymałeś wynagrodzenie w wysokości %s",
noRecog = "Nie poznajesz tej osoby.",
curTime = "Aktualny czas to %s.",
vendorEditor = "Edytor Sprzedawcy",
edit = "Edytuj",
disable = "Wyłącz",
vendorPriceReq = "Wprowadź nową cenę dla tego produktu.",
vendorEditCurStock = "Edytuj aktualny zapas",
vendorStockReq = "Wprowadź ile produktów powinno się znajdować maksymalnie w zasobie",
vendorStockCurReq = "Wprowadź ile przedmiotów jest dostępnych do sprzedarzy z całego zasobu.",
you = "Ty",
vendorSellScale = "Skala ceny sprzedaży",
vendorNoTrade = "Nie możesz dokonać wymiany z tym sprzedawcą!",
vendorNoMoney = "Sprzedawce nie stać na ten przedmiot!",
vendorNoStock = "Sprzedawca nie ma tego produktu aktualnie w asortymencie!",
contentTitle = "Nie znaleziono zawartości dla trybu Helix",
contentWarning = "Zawartość dla trybu Helix nie został wgrana. Rezultatem tego może być brak części funkcji.\nCzy chciałbyś otworzyć stronę warsztatu z daną zawartością?",
flags = "Flagi",
mapRestarting = "Restart mapy za %d sekund!",
chooseTip = "Wybierz postać do gry.",
deleteTip = "Usuń tą postać.",
storageInUse = "Ktoś inny używa tego teraz!",
storageSearching = "Przeszukiwanie...",
container = "Pojemnik",
containerPassword = "Ustawiłeś hasło tego pojemnika na %s.",
containerPasswordRemove = "Usunąłeś hasło z tego pojemnika.",
containerPasswordWrite = "Wprowadź hasło.",
containerName = "Ustawiłeś nazwę tego pojemnika na %s.",
containerNameWrite = "Wprowadź nazwę.",
containerNameRemove = "Usunąłeś nazwę z tego pojemnika.",
containerInvalid = "Musisz patrzeć na pojemnik, aby tego użyć!",
wrongPassword = "Wprowadziłeś złe hasło!",
respawning = "Odradzanie...",
tellAdmin = "Powiadom administrację o tym błędzie: %s",
mapAdd = "Dodałeś nową scenerie mapy.",
mapDel = "Usunąłęś %d scenerie mapy.",
mapRepeat = "Teraz dodaj punkt drugorzędny",
scoreboard = "Tabela",
ping = "Ping: %d",
viewProfile = "Obejrzyj profil Steam.",
copySteamID = "Skopiuj Steam ID",
money = "Pieniądze",
moneyLeft = "Twoje Pieniądze: ",
currentMoney = "Obecna ilość pieniędzy: ",
invalidClass = "To nie jest odpowiednia klasa!",
invalidClassFaction = "To nie jest poprawna klasa dla tej frakcji!",
miscellaneous = "Różne",
general = "Generalne",
observer = "Obserwator",
performance = "Wydajnośc",
thirdperson = "Trzecia osoba",
date = "Data",
interaction = "Interakcja",
server = "Serwer",
resetDefault = "Ustaw domyślnie",
resetDefaultDescription = "To zresetuje \"%s\" do swojej domyślej wartości \"%s\".",
optOpenBags = "Otwórz torbe z ekwipunkiem",
optdOpenBags = "Automatycznie pokazuje wszystkie torby w twoim ekwipunku gdy menu jest otwarte.",
optShowIntro = "Pokaż intro przy wchodzeniu na serwer",
optdShowIntro = "Pokazuje wstęp do Helixa następnym razem gdy będziesz wchodzić. Ta opcja jest zawsze wyłączona po tym gdy obejrzałeś wstęp.",
optCheapBlur = "Wyłącz rozmazanie",
optdCheapBlur = "Zastępuje rozmazanie interfejsu z zwykłym przyciemnieniem.",
optObserverTeleportBack = "Przywraca cię do poprzedniej lokalizacji",
optdObserverTeleportBack = "Przywraca cię do lokalizacji w której włączyłeś tryb obserwatora.",
optObserverESP = "Pokaż ESP administracyjne",
optdObserverESP = "Pokazuje nazwę i lokalizację każdego gracza na serwerze.",
opt24hourTime = "Używaj czasu 24-godzinnego",
optd24hourTime = "Pokazuj znacznik czasu w formacie 24-godzinnym, zamiast 12-godzinnego (AM/PM).",
optChatNotices = "Pokazuj uwagi/ogłoszenia na czacie",
optdChatNotices = "Przenosi wszystkie uwagi/ogłoszenia wyskakujące w prawym górnym rogu do czatu.",
optChatTimestamps = "Show timestamps in chat",
optdChatTimestamps = "Pokazuje godzinę wysłania przy każdej wiadomości na czacie.",
optAlwaysShowBars = "Zawsze pokazuj tabelkę z informacjami",
optdAlwaysShowBars = "Tworzy tabelkę z informacjami w lewym górnym rogu, bez znaczenia czy ma być ukryte czy nie.",
optAltLower = "Ukryj ręce gdy są opuszczone.",
optdAltLower = "Ukrywa ręce, gdy są opuszczone.",
optThirdpersonEnabled = "Włącz trzecią osobe",
optdThirdpersonEnabled = "Przenosi kamerę za ciebie. Również może być włączone w konsoli za pomocą \"ix_togglethirdperson\" ",
optThirdpersonClassic = "Włącz klasyczną trzecią osobe",
optdThirdpersonClassic = "Moves your character's view with your mouse.",
optThirdpersonVertical = "Wysokość kamery",
optdThirdpersonVertical = "Jak wysoko powinna być kamera.",
optThirdpersonHorizontal = "Wyrównanie kamery",
optdThirdpersonHorizontal = "Jak bardzo na lewo lub prawo powinna być kamera.",
optThirdpersonDistance = "Odległość kamery",
optdThirdpersonDistance = "Jak oddalona powinna być kamera.",
optThirdpersonCrouchOffset = "Kamera na wysokości kucania",
optdThirdpersonCrouchOffset = "Jak wysoko powinna być kamera podczas kucania.",
optDisableAnimations = "Wyłącz animacje",
optdDisableAnimations = "Zatrzymuje animację by zapewnić natychmiastowe przejście.",
optAnimationScale = "Skala animacji",
optdAnimationScale = "Jak bardziej szybko lub długo powinny być animacje.",
optLanguage = "Język",
optdLanguage = "Język interfejsu.",
optMinimalTooltips = "Minimalne powiadomienia z HUD'u",
optdMinimalTooltips = "Changes the HUD tooltip style to take up less space.",
optNoticeDuration = "Długość powiadomienia",
optdNoticeDuration = "Jak długo powinny wyświetlać się uwagi/ogłoszenia (w sekundach).",
optNoticeMax = "Maksimum powiadomień",
optdNoticeMax = "Ilość powiadomień zanim nadmiar zostanie usunięty.",
optChatFontScale = "Rozmiar czcionki",
optdChatFontScale = "Skalowanie czcionki na czacie.",
optChatOutline = "Obrysuj tekst w czacie",
optdChatOutline = " Obramowuje tekst czatu. Włącz to, jeśli masz problemy z czytaniem czatu.",
cmdRoll = "Losuje liczbę pomiędzy 0 a wyznaczoną liczbą.",
cmdPM = "Wysyła prywatną wiadomośc do kogoś.",
cmdReply = "Wysyła prywatną wiadomośc do ostatniej osoby, od której otrzymałeś wiadomość.",
cmdSetVoicemail = "Ustawia lub usuwa automatyczną odpowiedź gdy ktoś wysyła ci prywatną wiadomość.",
cmdCharGiveFlag = "Daje wyznaczoną flagę(i) do danej osoby.",
cmdCharTakeFlag = "Usuwa wyznaczoną flagę(i) osobie jeśli ją ma.",
cmdToggleRaise = "Podnosi albo opuscza broń, którą trzymasz.",
cmdCharSetModel = "Ustawia model postaci.",
cmdCharSetSkin = "Ustawia skórkę dla danej postaci.",
cmdCharSetBodygroup = "Ustawia bodygroupy dla danego modelu.",
cmdCharSetAttribute = "Ustawia poziom danego atrybutu dla osoby.",
cmdCharAddAttribute = "Dodaje poziom do danego atrybutu.",
cmdCharSetName = "Zmienia nazwę na wyznaczoną nazwę.",
cmdCharGiveItem = "Daje wyznaczony przedmiot osobie.",
cmdCharKick = "Zmusza osobę do wyjścia z jej postaci.",
cmdCharBan = "Zabrania osobie wchodzenia na danej postaci.",
cmdCharUnban = "Unban na zbanowaną postać.",
cmdGiveMoney = "Daje wyznaczoną ilość pieniędzy osobie, na którą patrzysz",
cmdCharSetMoney = "Zmienia ilośc pieniędzy do wyznaczonej ilośći.",
cmdDropMoney = "Wyrzuca wyznaczoną ilość pieniędzy przed tobą.",
cmdPlyWhitelist = "Pozwala osobie na stworzenie postaci w wyznaczonej frakcji.",
cmdCharGetUp = "Sprróbuj wstać po upadku.",
cmdPlyUnwhitelist = "Usuwa osobie whiteliste z danej frakcji.",
cmdCharFallOver = "Walisz salto na plecy",
cmdBecomeClass = "Zostań daną klasą w obecnej frakcji.",
cmdCharDesc = "Ustaw opis zewnętrzny postaci.",
cmdCharDescTitle = "Opis zewnętrzny",
cmdCharDescDescription = "Napisz opis zewnętrzny twojej postaci.",
cmdPlyTransfer = "Przenosi osobę do wyznaczonej frakcji.",
cmdCharSetClass = "Zmusza osobę do zostania wyznaczoną klasą w obecnej frakcji.",
cmdMapRestart = "Restartuje mape po wyznaczonej ilości czasu.",
cmdPanelAdd = "Dodaje Web Panel.",
cmdPanelRemove = "Usuwa Web Panel na który patrzysz.",
cmdTextAdd = "Dodaje napis.",
cmdTextRemove = "Usuwa napis na który patrzysz.",
cmdMapSceneAdd = "Dodaje punkt widokowy widoczny w menu postaci.",
cmdMapSceneRemove = "Usuwa punkt widokowy widoczny w menu postaci.",
cmdSpawnAdd = "Dodaje punkt odrodzenia danej frakcji.",
cmdSpawnRemove = "Usuwa punkt odrodzenia na który patrzysz.",
cmdAct = "Wykonuje animację %s .",
cmdContainerSetPassword = "Ustawia hasło kontenera na który patrzysz.",
cmdDoorSell = "Sprzedaje drzwi na które patrzysz.",
cmdDoorBuy = "Kupuje drzwi na które patrzysz.",
cmdDoorSetUnownable = "Ustawia drzwi na które patrzysz na niemożliwe do posiadania.",
cmdDoorSetOwnable = "Ustawia drzwi na które patrzysz na możliwe do posiadania.",
cmdDoorSetFaction = "Ustawia drzwi na które patrzysz na posiadane przed daną frakcję.",
cmdDoorSetDisabled = "Zabrania wykonywania komend na drzwi na które patrzysz.",
cmdDoorSetTitle = "Ustawia opis drzwi na które patrzysz.",
cmdDoorSetParent = "Ustawia właściciela danych drzwi.",
cmdDoorSetChild = "Ustawia podwłaścicieli danych drzwi.",
cmdDoorRemoveChild = "Usuwa podwłaściciela danych drzwi.",
cmdDoorSetHidden = "Ukrywa opis drzwi na które patrzysz. Wciąż są możliwe do posiadania.",
cmdDoorSetClass = "Ustawia drzwi na które patrzysz na posiadane przez daną klasę.",
cmdMe = "Wykonaj akcję fizyczną.",
cmdIt = "Wykonaj akcję twojego otoczenia.",
cmdW = "Szeptaj.",
cmdY = "Krzycz.",
cmdEvent = "Wykonuje akcję, którą każdy widzi.",
cmdOOC = "Wysyła wiadomość na czacie out-of-character.",
cmdLOOC = "Wysyła wiadomość na lokalnym czacie out-of-character."
}
================================================
FILE: gamemode/languages/sh_portuguese.lua
================================================
NAME = "Portuguese"
LANGUAGE = {
helix = "Helix",
introTextOne = "fist industries apresenta",
introTextTwo = "em colaboração com %s",
introContinue = "pressione espaço para continuar",
helpIdle = "Selecione uma categoria",
helpCommands = "Parametros de comando são são requiridos, enquanto [colchetes] são opcionais.",
helpFlags = "As flags com um fundo verde são acessíveis por este personagem.",
creditSpecial = "Agradecimentos especiais",
creditLeadDeveloper = "Desenvolvedor Principal",
creditUIDesigner = "Designer de interface do usuário",
creditManager = "Gestor de projeto",
creditTester = "Testador principal",
chatTyping = "Escrevendo...",
chatTalking = "Falando...",
chatYelling = "Gritando...",
chatWhispering = "Cochichando...",
chatPerforming = "Atuando...",
chatNewTab = "Nova aba...",
chatReset = "Redefinir posição",
chatResetTabs = "Redefinir abas",
chatCustomize = "Customizar...",
chatCloseTab = "Fechar aba",
chatTabName = "Nome da aba",
chatAllowedClasses = "Classe de bate-papo permitidas",
chatTabExists = "Uma aba de chat com esse nome já existe!",
chatMarkRead = "Marcar tudo como lido",
community = "Comunidade",
checkAll = "Selecionar tudo",
uncheckAll = "Deselecionar tudo",
color = "Cor",
type = "Tipo",
display = "Visualização",
loading = "Carregando",
dbError = "Falha na conexão com o banco de dados",
unknown = "Desconhecido",
noDesc = "Sem descrição disponível",
create = "Criar",
update = "Atualizar",
load = "Carregar personagem",
loadTitle = "Carregar um personagem",
leave = "Sair",
leaveTip = "Sair deste servidor.",
["return"] = "Retornar",
returnTip = "Retornar para o menu anterior.",
proceed = "Prosseguir",
faction = "Facção",
skills = "Habilidades",
choose = "Escolher",
chooseFaction = "Escolha uma facção",
chooseDescription = "Descreva a aparência do seu personagem",
chooseSkills = "Ajuste suas habilidades",
name = "Nome",
description = "Descrição",
model = "Modelo",
attributes = "Atributos",
attribPointsLeft = "Pontos restantes",
charInfo = "Character Information",
charCreated = "Você criou seu personagem com sucesso.",
charCreateTip = "Preencha os campos abaixo e pressione 'Finalizar' para criar o seu personagem.",
invalid = "Você providênciou um %s inválido!",
nameMinLen = "Seu nome deve conter ao menos %d caracteres!",
nameMaxLen = "Seu nome não pode ser maior que %d caracteres!",
descMinLen = "Sua descrição precisa conter ao menos %d caracteres!",
maxCharacters = "Você não pode criar mais personagens!",
player = "Jogador",
finish = "Finalizar",
finishTip = "Finalizar a criação do personagem.",
needModel = "Você precisa escolher um modelo válido!",
creating = "O seu personagem está sendo criado...",
unknownError = "Um erro desconhecido ocorreu!",
areYouSure = "Você tem certeza?",
delete = "Deletar",
deleteConfirm = "Este personagem será irrevogavelmente removido!",
deleteComplete = "%s foi deletado.",
no = "Não",
yes = "Sim",
close = "Fechar",
save = "Salvar",
itemInfo = "Nome: %s\nDescrição: %s",
itemCreated = "Item(ns) criado com sucesso.",
cloud_no_repo = "O repositório fornecido não é válido!",
cloud_no_plugin = "O plugin providênciado não é válido!",
inv = "Inventário",
plugins = "Plugins",
author = "Autor",
version = "Versão",
characters = "Personagens",
business = "Négocios",
settings = "Opções",
config = "Configuração",
chat = "Chat",
appearance = "Aparência",
misc = "Diversos",
oocDelay = "Você precisa aguardar %s mais segundos antes de usar o OOC novamente!",
loocDelay = "Você precisa aguardar %s mais segundos antes de usar o LOOC novamente!",
usingChar = "Você já está utilizando este personagem.",
notAllowed = "Você não tem permissão para fazer isso!",
itemNoExist = "O item que você solicitou não existe!",
cmdNoExist = "Esse comando não existe!",
charNoExist = "Um personagem correspondente não foi encontrado!",
plyNoExist = "Um jogador correspondente não foi encontrado!",
cfgSet = "%s definiu \"%s\" para %s.",
drop = "Soltar",
dropTip = "Solta esse item do seu inventário.",
take = "Pegar",
takeTip = "Pega este item e coloca no seu inventário.",
dTitle = "Porta sem proprietário",
dTitleOwned = "Porta Comprada",
dIsNotOwnable = "Esta porta não pode ser comprada.",
dIsOwnable = "Você pode comprar esta porta pressionando F2.",
dMadeUnownable = "Você tornou esta porta 'não comprável'.",
dMadeOwnable = "Você tornou esta porta 'comprável'.",
dNotAllowedToOwn = "Você não tem permissão para possuir esta porta!",
dSetDisabled = "Você tornou esta porta 'desativada'.",
dSetNotDisabled = "Você tornou esta porta 'ativada'.",
dSetHidden = "Você tornou esta porta oculta.",
dSetNotHidden = "Você tornou esta porta não mais oculta.",
dSetParentDoor = "Você definiu esta porta como sua porta mãe.",
dCanNotSetAsChild = "Você não pode definir a porta dos pais sendo criança!",
dAddChildDoor = "Você adicionou essa porta como criança.",
dRemoveChildren = "Você removeu todas as crianças desta porta.",
dRemoveChildDoor = "Você removeu esta porta de ser uma criança.",
dNoParentDoor = "Você não tem uma porta mãe estabelecida.",
dOwnedBy = "Esta porta é de propriedade de %s.",
dConfigName = "Portas",
dSetFaction = "Esta porta agora pertence a facção %s.",
dRemoveFaction = "Esta porta não pertence mais a nenhuma facção.",
dNotValid = "Você não está olhando para nenhuma porta válida!",
canNotAfford = "Você não pode pagar por isto!",
dPurchased = "Você comprou essa porta por %s.",
dSold = "Você vendeu esta porta por %s.",
notOwner = "Você não é o dono disso!",
invalidArg = "Você forneceu um valor inválido para o argumento #%s!",
invalidFaction = "A facção que você forneceu não pôde ser encontrada!",
flagGive = "%s atribuiu a %s as flags '%s'.",
flagGiveTitle = "Dar Flags",
flagTake = "%s revogou as flags '%s' de %s.",
flagTakeTitle = "Revogar Flags",
flagNoMatch = "Você precisa possuir as flags \"%s\" para realizar esta ação!",
textAdded = "Você adicionou um texto.",
textRemoved = "Você removeu o(s) texto(s) %s .",
moneyTaken = "Você recebeu %s.",
moneyGiven = "Você deu %s.",
insufficientMoney = "Você não tem o suficiente para fazer isso!",
businessPurchase = "Você comprou %s por %s.",
businessSell = "Você vendeu %s por %s.",
businessTooFast = "Por favor espere antes de comprar outro item!",
cChangeModel = "%s mudou o modelo de %s para %s.",
cChangeName = "%s mudou o nome de %s para %s.",
cChangeSkin = "%s mudou a 'skin' de %s para %s.",
cChangeGroups = "%s mudou o 'bodygroup' \"%s\" de %s para %s.",
cChangeFaction = "%s mudou %s para a facção %s.",
playerCharBelonging = "Este objeto pertençe ao seu outro personagem!",
spawnAdd = "Você adiconou um ponto de ressurgimento para %s.",
spawnDeleted = "Você removeu %s ponto(s) de ressurgimento.",
someone = "Alguém",
rgnLookingAt = "Permite que a pessoa que você está olhando reconheça você.",
rgnWhisper = "Permita que te reconheça aqueles que te escutam sussurar.",
rgnTalk = "Permite que aqueles que te escutam falando reconheça você.",
rgnYell = "Permite que aqueles que te escutam gritando rechoneça você.",
icFormat = "%s disse \"%s\"",
rollFormat = "%s jogou os dados e tirou %s.",
wFormat = "%s susurra \"%s\"",
yFormat = "%s grita \"%s\"",
sbOptions = "Clique para ver opções de %s.",
spawnAdded = "Você adicionou um ponto de ressurgimento para %s.",
whitelist = "%s adicionou %s à facção %s.",
unwhitelist = "%s retirou %s da facção %s.",
noWhitelist = "Você não tem permissão para usar este personagem!",
gettingUp = "Você está se levantando...",
wakingUp = "Você está retomando consciência...",
Weapons = "Armas",
checkout = "Ir ao Cheque (%s)",
purchase = "Comprar",
purchasing = "Comprando...",
success = "Sucesso",
buyFailed = "Falha na compra!",
buyGood = "Compra sucesiva!",
shipment = "Carga",
shipmentDesc = "Esta carga pertence à %s.",
class = "Classe",
classes = "Classes",
illegalAccess = "Acesso Ilegal.",
becomeClassFail = "Você não pode se tornar um(a) %s!",
becomeClass = "Você se tornou em um(a) %s.",
setClass = "Você mudou a classe de %s para %s.",
attributeSet = "Estabeleceu o atributo de %s de %s à %s.",
attributeNotFound = "Você especificou um atributo inválido!",
attributeUpdate = "You added %s's %s by %s.",
noFit = "Você não tem espaço o suficiente para este item!",
itemOwned = "You cannot interact with an item that you own on a different character!",
help = "Ajuda",
commands = "Comandos",
doorSettings = "Propiedades da Porta",
sell = "Vender",
access = "Acessar",
locking = "Trancando esta entidade...",
unlocking = "Destrancando esta entidade...",
modelNoSeq = "Seu modelo não suporta esta ação!",
notNow = "Você não tem permissão para fazer isto agora!",
faceWall = "Você precisa encarar uma parede para fazer isto!",
faceWallBack = "Você precisa estar contra a parede para fazer isto!",
descChanged = "Você mudou a descrição do seu personagem.",
noOwner = "O dono é inválido!",
invalidItem = "Você especificou um item inválido!",
invalidInventory = "Você especificou um inventário inválido!",
home = "Início",
charKick = "%s expulsou o personagem %s.",
charBan = "%s baniu o personagem %s.",
charBanned = "Este personagem está banido.",
playerConnected = "%s conectou-se ao servidor.",
playerDisconnected = "%s desconectou-se do servidor.",
setMoney = "Você ajustou o dinheiro de %s para %s.",
itemPriceInfo = "Você pode comprar este item por %s.\nVocê pode vender este item por %s",
free = "Grátis",
vendorNoSellItems = "Não há items para vender.",
vendorNoBuyItems = "Não há items para comprar.",
vendorSettings = "Propiedades do Fornecedor",
vendorUseMoney = "Fornecedor deve usar dinheiro?",
vendorNoBubble = "Esconder o balão do vendedor?",
mode = "Modo",
price = "Preço",
stock = "Estoque",
none = "Nenhum",
vendorBoth = "Comprar e Vender",
vendorBuy = "Apenas Comprar",
vendorSell = "Apenas Vender",
maxStock = "Estoque Máximo",
vendorFaction = "Editar Facção",
buy = "Comprar",
vendorWelcome = "Bem-vindo, o que posso lhe oferecer hoje?",
vendorBye = "Até mais!",
charSearching = "Você já está procurando um outro personagem!",
charUnBan = "%s desbaniu o personagem %s.",
charNotBanned = "Este personagem não está banido!",
quickSettings = "Configurações Rápidas",
vmSet = "Você ajustou seu correio de voz.",
vmRem = "Você removeu seu correio de voz.",
noPerm = "Você não é permitido a fazer isto!",
youreDead = "Você está Morto.",
injMajor = "Parece críticamente lesionado",
injLittle = "Parece lesionado",
chgName = "Mudar Nome",
chgNameDesc = "Insira o nome do personagem abaixo.",
weaponSlotFilled = "Você não pode usar outra arma de %s!",
equippedBag = "Você não pode mover uma bolsa que tem um item equipado!",
equippedWeapon = "Você não pode mover uma arma que está sendo equipada!",
nestedBags = "Você não pode colocar um inventário dentro de outro!",
outfitAlreadyEquipped = "Você já está vestindo este tipo de roupa!",
useTip = "Usa este item.",
equip = "Equipar",
equipTip = "Equipa o item..",
unequip = "Desequipar",
unequipTip = "Desequipa o item.",
consumables = "Consumíveis",
plyNotValid = "Você não está olhando para um jogador válido!",
restricted = "Você foi preso.",
salary = "Você recebeu %s do seu salário.",
noRecog = "Você não reconhece esta pessoa.",
curTime = "A hora é %s.",
vendorEditor = "Editar Fornecedor",
edit = "Editar",
disable = "Desativar",
vendorPriceReq = "Insira um novo preço para este item.",
vendorEditCurStock = "Edite o Estoque Atual",
vendorStockReq = "Insira a quantidade Máxima de Estoque o item deve ter.",
vendorStockCurReq = "Insira quantos itens estão disponíveis para compra do estoque máximo.",
you = "Você",
vendorSellScale = "Escala do preço de venda",
vendorNoTrade = "Você não consegue trocar com este fornecedor!",
vendorNoMoney = "Este fornecedor não consegue pagar este item!",
vendorNoStock = "Este fornecedor não possui esse item no estoque!",
contentTitle = "Faltando Conteúdo do Helix",
contentWarning = "Você não tem o conteúdo do Helix instalado. Isto pode resultar na falta de alguns aspectos.\nVocê gostaria de abrir a página da Workshop para baixar o conteúdo?",
flags = "Flags",
mapRestarting = "O mapa irá reiniciar em %d segundos!",
chooseTip = "Escolha este personagem para jogar.",
deleteTip = "Delete este personagem.",
storageInUse = "Alguém já está usando isto!",
storageSearching = "Procurando...",
container = "Container",
containerPassword = "Você ajustou a senha deste container para %s.",
containerPasswordRemove = "Você retirou a senha deste container.",
containerPasswordWrite = "Insira a senha.",
containerName = "Você mudou o nome deste container para %s.",
containerNameWrite = "Insira o nome.",
containerNameRemove = "Você removeu o nome deste container.",
containerInvalid = "Você precisa estar olhando para um container para fazer isto!",
wrongPassword = "Você inseriu uma senha errada!",
respawning = "Ressurgindo...",
tellAdmin = "Informe um membro da staff sobre este erro: %s",
scoreboard = "Scoreboard",
ping = "Ping: %d",
viewProfile = "Ver Perfil da Steam",
copySteamID = "Copiar Steam ID",
money = "Dinheiro",
moneyLeft = "Seu Dinheiro: ",
currentMoney = "Dinheiro Restante: ",
invalidClass = "Essa não é uma classe válida!",
invalidClassFaction = "Essa não é uma classe válida para esta facção!",
miscellaneous = "Diversos",
general = "Geral",
observer = "Observador",
performance = "Performance",
thirdperson = "Terceira Pessoa",
date = "Data",
interaction = "Interação",
server = "Servidor",
resetDefault = "Reiniciar para o padrão",
resetDefaultDescription = "Isto irá reiniciar \"%s\" ao seu valor padrão de \"%s\".",
optOpenBags = "Abrir bolsa com inventário",
optdOpenBags = "Automáticamente vê todas as bolsas em seu inventário quando o menu é aberto.",
optShowIntro = "Mostrar intro ao entrar",
optdShowIntro = "Mostra a introdução do Helix na próxima vez que entrar. Isto é sempre desativado depois que já assiste.",
optCheapBlur = "Desativar embaçamento",
optdCheapBlur = "Substitui o embaçamento da UI com uma ofuscação simples.",
optObserverTeleportBack = "Retornar ao último local",
optdObserverTeleportBack = "Retorna você ao local que você usou o modo observador..",
optObserverESP = "Mostrar admin ESP",
optdObserverESP = "Mostra o nome e o local de cada jogador no servidor.",
opt24hourTime = "Usar horário de 24 horas.",
optd24hourTime = "Mostra horários em formato de 24 horas, ao em vez de 12 (AM/PM).",
optChatNotices = "Mostrar noticias no chat",
optdChatNotices = "Coloca todas as notícias que aparecem no topo da tela no chat.",
optChatTimestamps = "Mostrar horários no chat",
optdChatTimestamps = "Acopla o horário à cada mensagem enviada no chat.",
optAlwaysShowBars = "Sempre mostrar barras de informação",
optdAlwaysShowBars = "Mostra as informações de barra no topo esquerdo da tela, mesmo se estiverem escondidas.",
optAltLower = "Esconder mãos quando abaixadas", -- @todo remove me
optdAltLower = "Esconde suas mãos quando elas estão ao lado.", -- @todo remove me
optThirdpersonEnabled = "Ativar terceira pessoa",
optdThirdpersonEnabled = "Move a câmera para atrás de você. Também pode ser ativo com o comando \"ix_togglethirdperson\".",
optThirdpersonClassic = "Ativar terceira pessoa clássico",
optdThirdpersonClassic = "Move a visão do seu personagem com o mouse.",
optThirdpersonVertical = "Altura da câmera",
optdThirdpersonVertical = "O quão alto a câmera deve estar.",
optThirdpersonHorizontal = "Posição da câmera",
optdThirdpersonHorizontal = "O quão para a direita ou esquerda a câmera deve estar.",
optThirdpersonDistance = "Distância da câmera",
optdThirdpersonDistance = "O quão longe a câmera deve estar.",
optThirdpersonCrouchOffset = "Altura da câmera agachada.",
optdThirdpersonCrouchOffset = "O quão alto a câmera deve estar enquanto agachado.",
optDisableAnimations = "Desativar animações",
optdDisableAnimations = "Desativa as animações do menu do Helix para transições instantâneas..",
optAnimationScale = "Escala da Animação",
optdAnimationScale = "O quão rápido ou devagar as animações devem estar.",
optLanguage = "Língua",
optdLanguage = "A língua mostrada na UI do Helix.",
optMinimalTooltips = "Dicas minimalistas no HUD",
optdMinimalTooltips = "Muda o estilo de dicas no HUD para tomar menos espaço.",
optNoticeDuration = "Duração de noticia",
optdNoticeDuration = "Por quanto tempo mostrar noticias (em segundos).",
optNoticeMax = "Máximo de noticias",
optdNoticeMax = "O número máximo de noticias até que noticias passadas sejam removidas.",
optChatFontScale = "Tamanho da fonte do Chat",
optdChatFontScale = "O quão grande ou pequeno a fonte deve ser.",
optChatOutline = "Borda no texto do Chat",
optdChatOutline = "Desenha uma borda ao redor do texto mostrado no Chat. Ative caso não consiga ver o texto.",
cmdRoll = "Rola um número entre 0 e o número especificado.",
cmdPM = "Manda uma mensagem privada para alguém.",
cmdReply = "Manda uma mensagem privada para a última pessoa que lhe mandou uma mensagem.",
cmdSetVoicemail = "Ajuste ou remova a mensagem de resposta automática quando alguém lhe manda uma mensagem.",
cmdCharGiveFlag = "Dá a(s) flag(s) especificadas para alguém.",
cmdCharTakeFlag = "Remove a(s) flag(s) especificadas de alguém caso tenham.",
cmdToggleRaise = "Levanta ou abaixa a arma que está segurando.",
cmdCharSetModel = "Ajusta o modelo do personagem da pessoa.",
cmdCharSetSkin = "Ajusta a skin para o modelo do personagem da pessoa.",
cmdCharSetBodygroup = "Ajusta o bodygroup para o modelo do personagem da pessoa.",
cmdCharSetAttribute = "Ajusta o nível do atributo especificado para a pessoa.",
cmdCharAddAttribute = "Adiciona um nível ao atributo especificado para a pessoa.",
cmdCharSetName = "Muda o nome de alguém para o nome especificado.",
cmdCharGiveItem = "Dá o item especificado para alguém.",
cmdCharKick = "Faz alguém sair do personagem que está jogando forçadamente.",
cmdCharBan = "Proibe alguém de entrar no servidor com o personagem atual.",
cmdCharUnban = "Permite que um personagem que foi banido possa ser usado novamente.",
cmdGiveMoney = "Dá uma quantia de dinheiro especificada á pessoa que está olhando.",
cmdCharSetMoney = "Muda a quantia total de dinheiro de alguém para a quantia especificada.",
cmdDropMoney = "Deixa cair a quantia especificada de dinheiro em uma caixinha à sua frente.",
cmdPlyWhitelist = "Permite que alguém crie um personagem na facção especificada.",
cmdCharGetUp = "Tenta se levantar após ter caído.",
cmdPlyUnwhitelist = "Tira a permissão de alguém para criar um personagem na facção especificada.",
cmdCharFallOver = "Faz você cair.",
cmdBecomeClass = "Tenta se tornar parte da classe especificada na sua facção.",
cmdCharDesc = "Ajusta a tua descrição física.",
cmdCharDescTitle = "Descrição Física",
cmdCharDescDescription = "Insira a descrição física de teu personagem.",
cmdPlyTransfer = "Transfere alguém para a facção designada.",
cmdCharSetClass = "Força alguém a se tornar a classe especificada na facção da pessoa.",
cmdMapRestart = "Reinicia o mapa após um certo intervalo de tempo.",
cmdPanelAdd = "Coloca um painel da Web no mundo.",
cmdPanelRemove = "Remove o painel que está a olhar.",
cmdTextAdd = "Adiciona um bloco de texto no mundo.",
cmdTextRemove = "Remove o bloco de texto que está a olhar.",
cmdMapSceneAdd = "Adiciona uma câmera cinemática que pode ser vista do menu.",
cmdMapSceneRemove = "Removes a camera viewpoint that is shown in the character menu.",
cmdSpawnAdd = "Adiciona um ponto de ressurgimento para a facção especificada.",
cmdSpawnRemove = "Remove qualquer ponto de ressurgimento que esteja a olhar.",
cmdAct = "Faz a animação de %s",
cmdContainerSetPassword = "Ajuste a senha do container que está a olhar.",
cmdDoorSell = "Venda a porta que está a olhar.",
cmdDoorBuy = "Compra a porta que está a olhar.",
cmdDoorSetUnownable = "Faz a porta que está a olhar incomprável.",
cmdDoorSetOwnable = "Faz a porta que está a olhar comprável.",
cmdDoorSetFaction = "Designa a porta que está a olhar à uma facção especificada.",
cmdDoorSetDisabled = "Faz com que seja impossível realizar comandos na porta que está a olhar.",
cmdDoorSetTitle = "Coloca um título na porta que está a olhar.",
cmdDoorSetParent = "Define o parentesco de um par de portas.",
cmdDoorSetChild = "Define o herdeiro de um par de portas.",
cmdDoorRemoveChild = "Remove o herdeiro de um par de portas.",
cmdDoorSetHidden = "Esconde a descrição da porta que está a olhar, mas ainda permite que alguém a compre.",
cmdDoorSetClass = "Permite que a porta que está a olhar seja propiedade da classe especificada.",
cmdMe = "Faça uma ação física.",
cmdIt = "Faça algo ao seu redor realizar uma ação.",
cmdW = "Susurre algo para as pessoas próximas a você.",
cmdY = "Grite algo para as pessoas ao seu redor.",
cmdEvent = "Faça algo realizar uma ação que todos podem ver.",
cmdOOC = "Mande uma mensagem para o chat fora-de-personagem global.",
cmdLOOC = "Mande uma mensagem para o chat dentro-de-personagem local."
}
================================================
FILE: gamemode/languages/sh_russian.lua
================================================
--Maybe this thing have some trouble with translate
NAME = "Русский"
LANGUAGE = {
helix = "Helix",
introTextOne = "Fist Industries представляет",
introTextTwo = "В сотрудничестве с %s",
introContinue = "Чтобы продолжить, нажмите Пробел",
helpIdle = "Выберите категорию",
helpCommands = "Параметры команды отмеченные <стрелками> обязательны, тогда как [скобки] опциональны.",
helpFlags = "Флаги с зеленым фоном обозначают доступные этому персонажу флаги.",
creditSpecial = "Особая благодарность",
creditLeadDeveloper = "Ведущий разработчик",
creditUIDesigner = "Дизайнер интерфейса",
creditManager = "Менеджер проекта",
creditTester = "Ведущий тестировщик",
chatTyping = "Пишет...",
chatTalking = "Говорит...",
chatYelling = "Кричит...",
chatWhispering = "Шепчет...",
chatPerforming = "Выполняет действие...",
chatNewTab = "Новая вкладка...",
chatReset = "Сбросить позицию",
chatResetTabs = "Сбросить позицию вкладок",
chatCustomize = "Настроить...",
chatCloseTab = "Закрыть вкладку",
chatTabName = "Имя вкладки",
chatNewTabTitle = "Новая Вкладка",
chatAllowedClasses = "Разрешенные классы чата",
chatTabExists = "Вкладка чата с таким именем уже существует!",
chatMarkRead = "Отметить как прочитанные",
community = "Сообщество",
checkAll = "Отметить все",
uncheckAll = "Убрать все",
color = "Цвет",
type = "Тип",
display = "Изображение",
loading = "Загрузка",
dbError = "Сбой подключения к базе данных",
unknown = "Неизвестный",
noDesc = "Нет описания",
create = "Создать",
update = "Обновить",
load = "Загрузить персонажа",
loadTitle = "Загрузить персонажа.",
leave = "Выйти с сервера",
leaveTip = "Покинуть сервер.",
["return"] = "Вернуться",
returnTip = "Вернуться в предыдущее меню.",
proceed = "Продолжить",
faction = "Фракция",
skills = "Навыки",
choose = "Выбрать",
chooseFaction = "Выберите фракцию",
chooseDescription = "Опишите свою внешность",
chooseSkills = "Выберите ваши навыки",
name = "Имя",
description = "Описание",
model = "Модель",
attributes = "Атрибуты",
attribPointsLeft = "Очки",
Endurance = "Выносливость",
Stamina = "Быстроходность",
Strength = "Сила",
charInfo = "Информация о персонаже",
charCreated = "Персонаж успешно создан.",
charCreateTip = "Заполните поля ниже и нажмите 'Создать' чтобы создать персонажа.",
invalid = "Вы ввели недействительный %s!",
nameMinLen = "Имя не должно быть короче %d символов!",
nameMaxLen = "Имя не должно быть длиннее %d символов!",
descMinLen = "Описание не должно быть короче %d символов!",
maxCharacters = "Достигнут лимит созданных персонажей!",
player = "Игрок",
finish = "Создать",
finishTip = "Закончить создание персонажа.",
needModel = "Выберите одну из моделей!",
creating = "Персонаж создается...",
unknownError = "Произошла неизвестная ошибка!",
areYouSure = "Вы уверены?",
delete = "Удалить",
deleteConfirm = "Этот персонаж будет безвозвратно удален!",
deleteComplete = "%s был удален.",
no = "Нет",
yes = "Да",
close = "Закрыть",
save = "Сохранить",
itemInfo = "Имя: %s\nОписание: %s",
itemCreated = "Предмет(ы) успешно создан(ы).",
cloud_no_repo = "Данный репозиторий недействителен!",
cloud_no_plugin = "Данный плагин недействителен!",
inv = "Инвентарь",
plugins = "Плагины",
author = "Автор",
version = "Версия",
characters = "Меню персонажей",
business = "Бизнес",
settings = "Настройки",
config = "Конфигурация",
chat = "Чат",
appearance = "Описание",
misc = "Остальное",
oocDelay = "Подождите %s секунд чтобы снова написать в OOC чат!",
loocDelay = "Подождите %s секунд чтобы снова написать в LOOC чат!",
usingChar = "Вы уже используете этого персонажа.",
notAllowed = "Эта команда вам недоступна!",
itemNoExist = "Запрошенный предмет не существует!",
cmdNoExist = "Такой команды не существует!",
charNoExist = "Такой персонаж не найден!",
plyNoExist = "Такой игрок не найден!",
cfgSet = "%s установил с \"%s\" на %s.",
drop = "Выбросить",
dropTip = "Выбросить предмет из инвентаря.",
take = "Взять",
takeTip = "Взять этот предмет в инвентарь.",
dTitle = "Дверь",
dTitleOwned = "Купленная дверь",
dIsNotOwnable = "Эта дверь не имеет владельца.",
dIsOwnable = "Вы можете приобрести эту дверь, нажав F2.",
dMadeUnownable = "Вы сделали эту дверь непокупаемой.",
dMadeOwnable = "Вы сделали эту дверь приобретаемой.",
dNotAllowedToOwn = "Вы не можете владеть этой дверью!",
dSetDisabled = "Вы заблокировали данную дверь.",
dSetNotDisabled = "Вы разблокировали данную дверь.",
dSetHidden = "Вы скрыли описание данной двери.",
dSetNotHidden = "Вы раскрыли описание данной двери.",
dSetParentDoor = "Вы сделали эту дверь родительской.",
dCanNotSetAsChild = "Вы не можете сделать родительскую дверь дочерней!",
dAddChildDoor = "Вы сделали эту дверь дочерней.",
dRemoveChildren = "Вы удалили у этой двери ее дочерние двери.",
dRemoveChildDoor = "Вы удалили дочернюю связь у этой двери.",
dNoParentDoor = "У вас нет родительской двери для связи.",
dOwnedBy = "Эта дверь принадлежит %s.",
dConfigName = "Двери",
dSetFaction = "Эта дверь теперь принадлежит фракции %s.",
dRemoveFaction = "Эта дверь больше не принадлежит ни одной фракции.",
dNotValid = "Вы не смотрите на валидную дверь!",
canNotAfford = "Вы не можете позволить это себе!",
dPurchased = "Вы купили эту дверь за %s.",
dSold = "Вы продали эту дверь за %s.",
notOwner = "Вы не владелец этой двери!",
invalidArg = "Указано недопустимое значение для аргумента #%s!",
invalidFaction = "Указанная фракция не найдена!",
flagGive = "%s выдал игроку %s флаги'%s'.",
flagGiveTitle = "Выдать флаги",
flagTake = "%s забрал у игрока '%s' флаги %s.",
flagTakeTitle = "Забрать флаги",
flagNoMatch = "Вы должны иметь \"%s\" флаш(и) чтобы сделать это!",
textAdded = "Текст добавлен.",
textRemoved = "Вы удалили %s текст(ов).",
moneyTaken = "Вы получили %s.",
moneyGiven = "Вы отдали %s.",
insufficientMoney = "У вас не хватает денег, чтобы сделать это!",
businessPurchase = "Вы купили %s за %s.",
businessSell = "Вы продали %s за %s.",
businessTooFast = "Подождите, прежде чем покупать другой товар!",
cChangeModel = "%s сменил %s модель на %s.",
cChangeName = "%s сменил %s имя на %s.",
cChangeSkin = "%s сменил %s скин на %s.",
cChangeGroups = "%s сменил %s \"%s\" на бодигруппу %s.",
cChangeFaction = "%s перенес персонажа %s во фракцию %s ",
playerCharBelonging = "Этот предмет принадлежит другому вашему персонажу!",
spawnAdd = "Вы добавили спаун для фракции%s.",
spawnDeleted = "Вы удалили %s спаун(ов).",
someone = "Кто-то",
rgnLookingAt = "Представиться человеку, на которого вы смотрите.",
rgnWhisper = "Представиться людям в радиусе шепота.",
rgnTalk = "Представиться людям в радиусе разговора.",
rgnYell = "Представиться людям в радиусе крика.",
icFormat = "%s говорит \"%s\"",
rollFormat = "%s получил число %s из %s.",
wFormat = "%s шепчет \"%s\"",
yFormat = "%s кричит \"%s\"",
sbOptions = "Нажмите, чтобы посмотреть настройки %s.",
spawnAdded = "Вы добавили спаун для %s.",
whitelist = "%s выдал вайтлист %s для фракции %s.",
unwhitelist = "%s забрал вайтлист у %s фракции %s.",
noWhitelist = "У вас нет вайтлиста для этого персонажа!",
gettingUp = "Вы поднимаетесь...",
wakingUp = "Вы приходите в себя...",
Ammunition = "Боеприпасы",
Storage = "Вместилища",
Outfit = "Одежда",
Weapons = "Оружие",
checkout = "Оформить заказ (%s)",
purchase = "Купить",
purchasing = "Покупка...",
success = "Успешно",
buyFailed = "Покупка не выполнена!",
buyGood = "Покупка выполнена!",
shipment = "Товар",
shipmentDesc = "Этот товар принадлежит %s.",
class = "Класс",
classes = "Классы",
illegalAccess = "Незаконный доступ.",
becomeClassFail = "Вы не можете выбрать класс %s!",
becomeClass = "Вы выбрали класс %s.",
setClass = "Вы установили игроку %s класс %s.",
attributeSet = "Вы сменили игроку %s %s на %s.",
attributeNotFound = "Вы указали неверный атрибут!",
attributeUpdate = "Вы добавили игроку %s %s, %s единицы.",
noFit = "Недостаточно места для этого предмета!",
itemOwned = "Вы не можете взаимодействовать с предметом, который принадлежит другому вашему персонажу!",
help = "Помощь",
commands = "Команды",
doorSettings = "Настройки двери",
sell = "Продать",
access = "Доступ",
locking = "Обьект блокируется...",
unlocking = "Обьект разблокируется...",
modelNoSeq = "Ваша модель не поддерживает эту анимацию.",
notNow = "Вы не можете сделать это сейчас.",
faceWall = "Прислонитесь лицом к стене, чтобы выполнить эту анимацию.",
faceWallBack = "Прислонитесь спиной к стене, чтобы выполнить эту анимацию.",
descChanged = "Описание вашего персонажа успешно изменено.",
noOwner = "Неверный владелец!",
invalidItem = "Вы указали неверный предмет!",
invalidInventory = "Вы указали неверный инвентарь!",
home = "Домой",
charKick = "%s кикнул персонажа %s.",
charBan = "%s забанил персонажа %s.",
charBanned = "Этот персонаж забанен.",
charBannedTemp = "Этот персонаж временно забанен.",
playerConnected = "%s подключился к серверу.",
playerDisconnected = "%s отключился от сервера.",
setMoney = "Вы установили игроку %s баланс %s единиц.",
itemPriceInfo = "Вы можете приобрести этот предмет за %s.\nВы можете продать этот предмет за %s",
free = "Бесплатно",
vendorNoSellItems = "У торговца нету предметов для продажи.",
vendorNoBuyItems = "Нету предметов для покупки.",
vendorSettings = "Настройки торговца",
vendorUseMoney = "Торговец использует деньги?",
vendorNoBubble = "Убрать пузырек над головой?",
mode = "Режим",
price = "Цена",
stock = "Кол-во",
none = "Нет",
vendorBoth = "Покупать и продавать",
vendorBuy = "Только покупать",
vendorSell = "Только продавать",
maxStock = "Максимальное кол-во",
vendorFaction = "Доступ фракций",
buy = "Купить",
vendorWelcome = "Приветствую вас в моем магазине. Желаете что-то купить?",
vendorBye = "Приходите еще!",
charSearching = "Вы уже ищите другого персонажа!",
charUnBan = "%s разбанил персонажа %s.",
charNotBanned = "Этот персонаж не забанен!",
quickSettings = "Быстрые настройки",
vmSet = "Вы настроили свою голосовую почту.",
vmRem = "Вы удалили свою голосовую почту.",
noPerm = "У вас нет прав чтобы это сделать!",
youreDead = "Вы погибли",
injMajor = "Очень сильно ранен",
injLittle = "Слегка ранен",
chgName = "Сменить имя",
chgNameDesc = "Введите новое имя персонажа.",
weaponSlotFilled = "Слот %s занять другим оружием!",
equippedBag = "Вы не можете переместить сумку с предметами внутри!",
equippedWeapon = "Вы не можете манипулировать экипированным оружием!",
nestedBags = "Вы не можете положить сумку с вещами в хранилище!",
outfitAlreadyEquipped = "Вы уже носите этот элемент экипировки!",
Load = "Зарядить",
useTip = "Использовать этот предмет.",
View = "Просмотреть",
Equip = "Экипировать",
equipTip = "Экипировать этот предмет.",
Unequip = "Стянуть",
unequipTip = "Стянуть этот предмет.",
consumables = "Расходные материалы",
plyNotValid = "Вы должны смотреть на существующего игрока!",
restricted = "Вы были связаны.",
salary = "Вы получили свою зарплату в размере %s.",
noRecog = "Вы не знаете этого персонажа.",
curTime = "Время: %s.",
vendorEditor = "Редактор торговца",
edit = "Изменить макс. кол-во",
disable = "Отключить",
vendorPriceReq = "Введите новую цену для этого предмета.",
vendorEditCurStock = "Изменить настоящее кол-во",
vendorStockReq = "Введите максимальное количество предметов.",
vendorStockCurReq = "Введите доступное количество предметов для покупки.",
you = "Персонаж",
vendorSellScale = "Множитель продажи",
vendorNoTrade = "Вы не можете торговать с этим торговцем!",
vendorNoMoney = "У торговца недостаточно денег для покупки!",
vendorNoStock = "У торговца нету этого товара!",
contentTitle = "Отсутствует контент Helix",
contentWarning = "У вас нет контента фреймворка Helix. Могут отсутствовать некоторые функции.\nХотите открыть страницу контента в Steam Workshop?",
flags = "Флаги",
mapRestarting = "Рестарт карты произойдет через %d секунд!",
chooseTip = "Выберите персонажа, чтобы начать играть.",
deleteTip = "Удалить этого персонажа.",
storageInUse = "Кто-то уже использует это!",
storageSearching = "Открываю...",
container = "Контейнер",
containerPassword = "На контейнер установлен замок с паролем %s.",
containerPasswordRemove = "Пароль с этого контейнера был убран.",
containerPasswordWrite = "Введите пароль.",
containerName = "Имя контейнера установлено %s.",
containerNameWrite = "Введите имя.",
containerNameRemove = "Имя контейнера убрано.",
containerInvalid = "Вы должны смотреть на контейнер, чтобы это выполнить это!",
wrongPassword = "Неверный пароль!",
respawning = "Респавнимся...",
tellAdmin = "Сообщите администрации эту ошибку: %s",
mapAdd = "Вы добавили сцену карты.",
mapDel = "Вы удалили сцену(ы) %d.",
mapRepeat = "Теперь добавьте вторую точку.",
scoreboard = "Игроки",
ping = "Пинг: %d",
viewProfile = "Открыть профиль Steam",
copySteamID = "Скопировать Steam ID",
money = "Наличные",
moneyLeft = "Ваши наличные: ",
currentMoney = "Баланс: ",
invalidClass = "Недопустимый класс!",
invalidClassFaction = "Недопустимый класс или фракция!",
miscellaneous = "Разное",
general = "Общие",
observer = "ESP и наблюдение",
performance = "Производительность",
thirdperson = "Третье лицо",
date = "Дата",
interaction = "Взаимодействие",
server = "Сервер",
resetDefault = "По умолчанию",
resetDefaultDescription = "Это сбросит значение \"%s\" до стандартного значения \"%s\".",
optOpenBags = "Открывать сумки в инвентаре ",
optdOpenBags = "При открытии меню, автоматически открывает все сумки в вашем инвентаре.",
optShowIntro = "Интро при подключении",
optdShowIntro = "Показывает интро фреймворка при следующем подключении. Автоматически отключается после просмотра.",
optCheapBlur = "Отключить блюр",
optdCheapBlur = "Заменяет блюр в интерфейсе на простое затемнение.",
optObserverTeleportBack = "Возвращение на место",
optdObserverTeleportBack = "Возвращает вас к месту, в котором вы вошли в режим наблюдения.",
optObserverESP = "Админ ESP",
optdObserverESP = "Показывает имена и местоположение каждого игрока на сервере.",
opt24hourTime = "24-часовой формат времени",
optd24hourTime = "Показывать время в 24-х часовом формате, вместо 12 часового (AM/PM).",
optChatNotices = "Уведомления в чате",
optdChatNotices = "Выводит все уведомления которые появляются в правом верхнем углу в текстовый чат.",
optChatTimestamps = "Время в чате",
optdChatTimestamps = "Отмечает время отправки кажого сообщения в чате.",
optAlwaysShowBars = "Всегда показывать информационную панель",
optdAlwaysShowBars = "Отображает состояние персонажа в левом верхнем углу на постоянной основе.",
optAltLower = "Опуская, скрывать руки", -- @todo remove me
optdAltLower = "Скрывать руки, при опускани оных.", -- @todo remove me
optThirdpersonEnabled = "|Третье лицо",
optdThirdpersonEnabled = "Перемещение камеры позади вас. Также переключается консольной командой \'ix_togglethirdperson\'",
optThirdpersonClassic = "|Классическая камера",
optdThirdpersonClassic = "Управлять взглядом вашего персонажа с помощью мыши.",
optThirdpersonVertical = "Высота камеры",
optdThirdpersonVertical = "Высота расположения камеры от 3 лица.",
optThirdpersonHorizontal = "Камера по горизонтали",
optdThirdpersonHorizontal = "Расположение камеры влево или вправо от персонажа.",
optThirdpersonDistance = "Расстояние камеры",
optdThirdpersonDistance = "Как далеко располагается камера от персонажа.",
optThirdpersonCrouchOffset = "Высота камеры при присяде",
optdThirdpersonCrouchOffset = "Высота расположения камеры от 3 лица.",
optDisableAnimations = "Отключить анимации интерфейса",
optdDisableAnimations = "Отключает анимации интерфейса, для резкого перехода между окнами.",
optAnimationScale = "Скорость анимаций",
optdAnimationScale = "Насколько быстрее или медленнее проигрывается анимация.",
optLanguage = "Язык",
optdLanguage = "Язык интерфейса Helix.",
optMinimalTooltips = "Минималистичные подсказки",
optdMinimalTooltips = "Изменяет стиль показа подсказок, чтобы занимать меньше места.",
optNoticeDuration = "Длительность уведомления",
optdNoticeDuration = "Как долго показываются уведомления (в секундах).",
optNoticeMax = "Максимальное кол-во уведомлений",
optdNoticeMax = "Количество уведомлений, показываемых одновременно.",
optChatFontScale = "Шрифт чата",
optdChatFontScale = "Насколько больше или меньше шрифт чата должен быть.",
optChatOutline = "Контур в чате",
optdChatOutline = "Рисует контур вокруг текста в чате. Включите, если у вас проблемы с чтением чата.",
cmdRoll = "Случайное число от 0 до указанного значения.",
cmdPM = "Отправить личное сообщение выбранному персонажу.",
cmdReply = "Отправить личное сообщение последнему человеку, от которого вы получили сообщение.",
cmdSetVoicemail = "Установить или убрать сообщение автоответчика, при отправке вам сообщения.",
cmdCharGiveFlag = "Дать указанному игроку флаг(и)",
cmdCharTakeFlag = "Забрать указанный(е) флаг(и) у игрока, если такие имеются.",
cmdToggleRaise = "Поднять или опустить оружие, которое вы удерживаете.",
cmdCharSetModel = "Установить выбранную модель игроку.",
cmdCharSetSkin = "Установить выбранный скин для модели игрока.",
cmdCharSetBodygroup = "Установить бодигруппу для модели игрока.",
cmdCharSetAttribute = "Установить уровень выбранного атрибута у игрока.",
cmdCharAddAttribute = "Добавить к уровню выбранное количество очков.",
cmdCharSetName = "Изменить имя пользователя на заданное.",
cmdCharGiveItem = "Дать заданный предмет игроку.",
cmdCharKick = "Кикнуть выбранного персонажа.",
cmdCharBan = "Ограничить игроку доступ к выбранному персонажу.",
cmdCharUnban = "Снимает ограничение на игру за выбранного персонажа",
cmdGiveMoney = "Передать сумму денег персонажу, на которого вы смотрите.",
cmdCharSetMoney = "Изменить сумму денег у персонажа на указанную.",
cmdDropMoney = "Положить определенную сумму денег в хранилище на которое вы смотрите.",
cmdPlyWhitelist = "Выдать доступ игроку к созданию персонажа в указанной фракции",
cmdCharGetUp = "Попытаться встать после падения.",
cmdPlyUnwhitelist = "Забрать доступ у игрока к созданию персонажа в указанной фракции.",
cmdCharFallOver = "Расслабиться и упасть на землю.",
cmdBecomeClass = "Взять выбранный класс в вашей текущей фракции.",
cmdCharDesc = "Установить физическое описание вашего персонажа.",
cmdCharDescTitle = "Физическое описание",
cmdCharDescDescription = "Введите физическое описание персонажа.",
cmdPlyTransfer = "Перевести выбранного персонажа в указанную фракцию.",
cmdCharSetClass = "Сменить выбранному игроку указанный класс в его фракции.",
cmdMapRestart = "Перезапустить карту через указанное время.",
cmdPanelAdd = "Создать веб изображение там, куда направлен взгляд.",
cmdPanelRemove = "Удалить веб изображение, на которое вы смотрите.",
cmdTextAdd = "Создать блок текста в мире.",
cmdTextRemove = "Удалить блоки текста, на которые вы смотрите.",
cmdMapSceneAdd = "Добавить кинематографичную камеру, в меню выбора персонажа.",
cmdMapSceneRemove = "Убрать кинематографичную камеру, в меню выбора персонажа.",
cmdSpawnAdd = "Добавить точку спавна для указанной фракции.",
cmdSpawnRemove = "Удалить все точки спавна, на которые вы смотрите.",
cmdAct = "Выполнить анимацию %s.",
cmdContainerSetPassword = "Установить пароль для контейнера, на который вы смотрите.",
cmdDoorSell = "Продать дверь, на которую вы смотрите.",
cmdDoorBuy = "Купить дверь, на которую вы смотрите.",
cmdDoorSetUnownable = "Сделать дверь, на которую вы смотрите, недоступной для покупки.",
cmdDoorSetOwnable = "Сделать дверь, на которую вы смотрите, доступной для приобретения.",
cmdDoorSetFaction = "Установить двери, на которую вы смотрите, принадлежность к фракции.",
cmdDoorSetDisabled = "Отключить возможность взаимодействия с дверью, на которую вы смотрите.",
cmdDoorSetTitle = "Установить заголовок двери, на которую вы смотрите.",
cmdDoorSetParent = "Выбрать родительскую дверь для дочерних дверей.",
cmdDoorSetChild = "Выбрать дочерние двери для родительской двери.",
cmdDoorRemoveChild = "Убрать у двери дочернюю зависимость.",
cmdDoorSetHidden = "Убирает описание двери на которую вы смотрите, но оставляет возможность владения.",
cmdDoorSetClass = "Установить двери, на которую вы смотрите, принадлежность к классу во фракции.",
cmdMe = "Выполнить физическое действие.",
cmdIt = "Совершить какое-либо действие вокруг себя.",
cmdW = "Прошептать что-то людям, стоящим рядом.",
cmdY = "Крикнуть что-то людям, стоящим в большом радиусе.",
cmdEvent = "Выполнить действие, которое увидят все игроки.",
cmdOOC = "Отправить сообщение в глобальный out-of-character чат .",
cmdLOOC = "Отправить сообщение в локальный out-of-character чат.",
Instructions = "Информация"
}
================================================
FILE: gamemode/languages/sh_spanish.lua
================================================
-- SPANISH TRANSLATION
-- Cuboxis (http://steamcommunity.com/id/Cuboxis)
-- Geferon (https://steamcommunity.com/id/GEFERON)
-- Whitehole (https://steamcommunity.com/id/whitehole)
-- Carlos Bes
NAME = "Español"
LANGUAGE = {
helix = "Helix",
introTextOne = "fist industries presenta",
introTextTwo = "en colaboración con %s",
introContinue = "pulsa espacio para continuar",
helpIdle = "Selecciona una categoría",
helpCommands = "Los parámetros de comando rodeados de son obligatorios, mientras que los rodeados de [corchetes] son opcionales.",
helpFlags = "Las Flags con el fondo verde son accesibles por este personaje.",
creditSpecial = "Muchas Gracias",
creditLeadDeveloper = "Desarrollador Principal",
creditUIDesigner = "Diseñador de la Interfaz de Usuario",
creditManager = "Jefe de Proyecto",
creditTester = "Tester Principal",
chatTyping = "Escribiendo...",
chatTalking = "Hablando...",
chatYelling = "Gritando...",
chatWhispering = "Susurrando...",
chatPerforming = "Actuando...",
chatNewTab = "Nueva pestaña...",
chatReset = "Restablecer posición",
chatResetTabs = "Restablecer pestañas",
chatCustomize = "Personalizar...",
chatCloseTab = "Cerrar Pestaña",
chatTabName = "Nombre de Pestaña",
chatNewTabTitle = "Nueva Pestaña",
chatAllowedClasses = "Categorías de chat permitidas",
chatTabExists = "¡Ya existe una pestaña de chat con ese nombre!",
chatMarkRead = "Marcar todo como leído",
community = "Comunidad",
checkAll = "Seleccionar todo",
uncheckAll = "Deseleccionar todo",
color = "Color",
type = "Tipo",
display = "Visualización",
loading = "Cargando",
dbError = "Fallo de conexión con la Base de Datos",
unknown = "Desconocido",
noDesc = "Descripción no disponible",
create = "Crear",
update = "Actualizar",
load = "Cargar personaje",
loadTitle = "Cargar un Personaje",
leave = "Abandonar",
leaveTip = "Salir del servidor.",
["return"] = "Volver",
returnTip = "Vuelve al menú anterior.",
proceed = "Continuar",
faction = "Facción",
skills = "Habilidades",
choose = "Escoger",
chooseFaction = "Escoge una Facción",
chooseDescription = "Describe la apariencia de tu personaje",
chooseSkills = "Ajusta tus habilidades",
name = "Nombre",
description = "Descripción",
model = "Modelo",
attributes = "Atributos",
attribPointsLeft = "Puntos restantes",
charInfo = "Información del personaje",
charCreated = "Has creado tu personaje correctamente.",
charCreateTip = "Completa todos los campos obligatorios y haz clic en 'Finalizar' para crear a tu personaje.",
invalid = "Has introducido un(a) %s inválido/a.",
nameMinLen = "Tu nombre debe de tener al menos %d caracteres.",
nameMaxLen = "Tu nombre no puede tener mas de %d caracteres.",
descMinLen = "Tu descripción debe tener al menos %d caracteres.",
maxCharacters = "¡No puedes crear mas personajes!",
player = "Jugador",
finish = "Finalizar",
finishTip = "Finaliza la creación de personaje.",
needModel = "Necesitas escoger un modelo válido.",
creating = "Tu personaje está siendo creado...",
unknownError = "Ha ocurrido un error desconocido",
areYouSure = "¿Estás seguro?",
delete = "Borrar",
deleteConfirm = "¡Este personaje será eliminado PERMANENTEMENTE!",
deleteComplete = "%s ha sido eliminado.",
no = "No",
yes = "Sí",
close = "Cerrar",
save = "Guardar",
itemInfo = "Nombre: %s\nDescripción: %s",
itemCreated = "Objeto(s) creado(s) correctamente.",
cloud_no_repo = "El repositorio introducido no es válido.",
cloud_no_plugin = "El plugin introducido no es válido.",
inv = "Inventario",
plugins = "Plugins",
pluginLoaded = "%s ha habilitado el plugin \"%s\" y se habilitará con el siguiente cambio de mapa.",
pluginUnloaded = "%s ha deshabilitado el plugin \"%s\" y se deshabilitará con el siguiente cambio de mapa.",
loadedPlugins = "Plugins Habilitados",
unloadedPlugins = "Plugins Deshabilitados",
on = "On",
off = "Off",
author = "Autor",
version = "Versión",
characters = "Personajes",
business = "Negocios",
settings = "Opciones",
config = "Configuración",
chat = "Chat",
appearance = "Apariencia",
misc = "Misceláneo",
oocDelay = "Debes esperar %s segundo(s) antes de usar de nuevo el chat OOC.",
loocDelay = "Debes esperar %s segundo(s) antes de usar de nuevo el chat LOOC.",
usingChar = "Ya estás usando éste personaje.",
notAllowed = "Lo siento, no tienes permitido hacer esto.",
itemNoExist = "Lo siento, el objeto especificado no existe.",
cmdNoExist = "Lo siento, este comando no existe.",
charNoExist = "Lo siento, el personaje especificado no ha podido ser encontrado.",
plyNoExist = "Lo siento, el jugador correspondiente no ha podido ser encontrado.",
cfgSet = "%s ha establecido \"%s\" a %s.",
drop = "Tirar",
dropTip = "Suelta el objeto de tu inventario.",
take = "Coger",
takeTip = "Pone este objeto en tu inventario.",
dTitle = "Puerta sin propietario",
dTitleOwned = "Puerta comprada",
dIsNotOwnable = "Esta puerta no puede tener propietario.",
dIsOwnable = "Puedes comprar esta puerta pulsando F2.",
dMadeUnownable = "Has hecho que esta puerta no pueda tener propietario.",
dMadeOwnable = "Has hecho que esta puerta pueda tener propietario.",
dNotAllowedToOwn = "No tienes permiso para hacer esta puerta de tu propiedad.",
dSetDisabled = "Has deshabilitado esta puerta.",
dSetNotDisabled = "Has habilitado esta puerta.",
dSetHidden = "Has hecho que esta puerta esté oculta.",
dSetNotHidden = "Has hecho que esta puerta no esté oculta.",
dSetParentDoor = "Has establecido esta puerta como puerta primaria.",
dCanNotSetAsChild = "No puedes establecer la puerta primaria como puerta secundaria.",
dAddChildDoor = "Has añadido esta puerta como puerta secundaria.",
dRemoveChildren = "Has eliminado todas las puertas secundarias de esta puerta.",
dRemoveChildDoor = "Has eliminado esta puerta como puerta secundaria.",
dNoParentDoor = "No tienes seleccionada una puerta primaria.",
dOwnedBy = "Esta puerta es propiedad de %s.",
dConfigName = "Puertas",
dSetFaction = "Esta puerta ahora pertenece a %s.",
dRemoveFaction = "Esta puerta ya no pertenece a ninguna facción.",
dNotValid = "No estás mirando a una puerta válida.",
canNotAfford = "No puedes permitirte comprar esto.",
dPurchased = "Has comprado esta puerta por %s.",
dSold = "Has vendido esta puerta por %s.",
notOwner = "Esto no te pertenece.",
invalidArg = "El valor introducido para el argumento #%s es inválido.",
invalidFaction = "La facción que has introducido no se ha podido encontrar.",
flagGive = "%s ha dado a %s las flags '%s'.",
flagGiveTitle = "Dar Flags",
flagTake = "%s ha quitado las flags '%s' de %s.",
flagTakeTitle = "Quitar Flags",
flagNoMatch = "Debes tener la(s) flag(s) \"%s\" para hacer esta acción.",
panelAdded = "Has añadido un panel.",
panelRemoved = "Has borrado %d panel(es).",
textAdded = "Has añadido un texto.",
textRemoved = "Has borrado %s texto(s).",
moneyTaken = "Has recibido %s.",
moneyGiven = "Has dado %s.",
insufficientMoney = "No puedes permitirte esto con tu dinero.",
belowMinMoneyDrop = "No puedes soltar menos de %s.",
businessPurchase = "Has comprado %s por %s.",
businessSell = "Has vendido %s por %s.",
businessTooFast = "Por favor, espera antes de comprar otro objeto.",
cChangeModel = "%s ha cambiado el modelo de %s a %s.",
cChangeName = "%s ha cambiado el nombre de %s a %s.",
cChangeSkin = "%s ha cambiado el 'skin' de %s a %s.",
cChangeGroups = "%s ha cambiado el 'bodygroup' \"%s\" de %s a %s.",
cChangeFaction = "%s ha transferido a %s a la facción %s.",
playerCharBelonging = "Este objeto ya pertenece a otro de tus personajes.",
spawnAdd = "Has añadido un punto de reaparición para %s.",
spawnDeleted = "Has eliminado un punto de reaparición de %s.",
someone = "Alguien",
rgnLookingAt = "Permite que te reconozca la persona a la que estás mirando.",
rgnWhisper = "Permite que te reconozcan aquellos que te escuchen susurrar.",
rgnTalk = "Permite que te reconozcan aquellos que te escuchen hablar.",
rgnYell = "Permite que te reconozcan aquellos que te escuchen gritar.",
icFormat = "%s dice \"%s\"",
rollFormat = "%s ha tirado los dados y ha sacado un %s.",
wFormat = "%s susurra \"%s\"",
yFormat = "%s grita \"%s\"",
sbOptions = "Haz clic para ver las opciones de %s.",
spawnAdded = "Has añadido punto de aparición de %s.",
whitelist = "%s ha añadido a %s en la lista blanca de la facción %s.",
unwhitelist = "%s ha eliminado a %s de la lista blanca de la facción %s.",
noWhitelist = "No tienes la lista blanca para la facción de este personaje.",
charNotWhitelisted = "%s no está en la lista blanca para la facción %s.",
gettingUp = "Te estás levantando...",
wakingUp = "Estás recuperando la consciencia...",
Weapons = "Armas",
checkout = "Ir a la cesta (%s)",
purchase = "Comprar",
purchasing = "Comprando...",
success = "Éxito.",
buyFailed = "Compra fallida.",
buyGood = "Compra exitosa!",
shipment = "Envío",
shipmentDesc = "Este envío pertenece a %s.",
class = "Clase",
classes = "Clases",
illegalAccess = "Acceso ilegal.",
becomeClassFail = "Fallo en convertirse en %s.",
becomeClass = "Te has convertido en %s.",
setClass = "Has establecido la clase de %s a %s.",
attributeSet = "Has establecido el nivel de %s de %s a %s puntos.",
attributeNotFound = "Has especificado un atributo inválido.",
attributeUpdate = "Has añadido al nivel de %s de %s %s puntos.",
noFit = "Este objeto no cabe en tu inventario.",
itemOwned = "No puedes interactuar con un objeto de tus otros personajes.",
help = "Ayuda",
commands = "Comandos",
doorSettings = "Configuración de puertas",
sell = "Vender",
access = "Acceso",
locking = "Bloqueando esta entidad...",
unlocking = "Desbloqueando esta entidad...",
modelNoSeq = "Tu modelo no es compatible con este acto.",
notNow = "No tienes permiso para hacer esto ahora.",
faceWall = "Debes estar mirando una pared para hacer esto.",
faceWallBack = "Tu espalda tiene que estar mirando una pared para hacer esto.",
descChanged = "Has cambiado la descripción de tu personaje.",
noOwner = "El propietario no es válido.",
invalidItem = "El objeto no es válido.",
invalidInventory = "El inventario no es válido.",
home = "Inicio",
charKick = "%s ha echado a %s.",
charBan = "%s ha bloqueado el personaje %s.",
charBanned = "Este personaje está bloqueado.",
charBannedTemp = "Este personaje está temporalmente bloqueado.",
playerConnected = "%s se ha conectado al servidor.",
playerDisconnected = "%s se ha desconectado del servidor.",
setMoney = "Has establecido el dinero de %s a %s.",
itemPriceInfo = "Puedes comprar este objeto por %s.\nPuedes vender este objeto por %s.",
free = "Gratis",
vendorNoSellItems = "No hay objetos para vender.",
vendorNoBuyItems = "No hay objetos para comprar.",
vendorSettings = "Configuración del Vendedor",
vendorUseMoney = "¿El vendedor debe usar dinero?",
vendorNoBubble = "¿Esconder burbuja del vendedor?",
category = "Categoría",
mode = "Modo",
price = "Precio",
stock = "Existencias",
none = "Nada",
vendorBoth = "Comprar y vender",
vendorBuy = "Sólo comprar",
vendorSell = "Sólo vender",
maxStock = "Existencias máximas",
vendorFaction = "Editor de facción",
buy = "Comprar",
vendorWelcome = "Bienvenido/a a mi tienda, ¿Qué puedo hacer por usted?",
vendorBye = "¡Vuelva pronto!",
charSearching = "Ya te encuentras buscando a otro personaje, por favor espera.",
charUnBan = "%s ha desbloqueado el personaje %s.",
charNotBanned = "Este personaje no está bloqueado.",
quickSettings = "Configuración rápida",
vmSet = "Has establecido tu buzón de voz.",
vmRem = "Has eliminado tu buzón de voz.",
noPerm = "No tienes permiso para hacer esto.",
youreDead = "Estás muerto.",
injMajor = "Parece que está gravemente herido.",
injLittle = "Parece que está herido.",
chgName = "Cambiar nombre",
chgNameDesc = "Introduce abajo el nuevo nombre del personaje.",
weaponSlotFilled = "No puedes usar otra arma %s!",
equippedBag = "La bolsa que has movido tiene un objeto que te has equipado.",
equippedWeapon = "¡No puedes mover un arma que tienes equipada actualmente!",
nestedBags = "¡No puedes poner un inventario dentro de un inventario de almacenamiento!",
outfitAlreadyEquipped = "¡Ya estas usando este tipo de ropa!",
useTip = "Usa el objeto.",
equip = "Equipar",
equipTip = "Equipa el objeto.",
unequip = "Desequipar",
unequipTip = "Desequipa el objeto.",
consumables = "Consumibles",
plyNotValid = "No estás mirando a un jugador válido.",
restricted = "Has sido atado.",
salary = "Has recibido %s de tu salario.",
noRecog = "No reconoces a esta persona.",
curTime = "La hora actual es %s.",
vendorEditor = "Editor de vendedor",
edit = "Editar",
disable = "Deshabilitar",
vendorPriceReq = "Introduce el nuevo precio del artículo.",
vendorEditCurStock = "Editar las existencias actuales",
vendorStockReq = "Introduce la cantidad máxima de existencias que debe tener el artículo.",
vendorStockCurReq = "Introduce cuantos artículos de las existencias máximas estan disponibles para comprar.",
you = "Tú",
vendorSellScale = "Escala de precio de venta",
vendorNoTrade = "¡No puedes intercambiar con este vendedor!",
vendorNoMoney = "Este vendedor no puede permitirse comprar ese artículo.",
vendorNoStock = "Este vendedor no tiene ese artículo en existencia.",
vendorMaxStock = "¡Este vendedor ya tiene las existencias máximas de ese artículo!",
contentTitle = "Falta el Contenido de Helix",
contentWarning = "No tienes el contenido de Helix montado. Puede que falten ciertas características.\n¿Quieres abrir la página de Workshop del contenido de Helix?",
flags = "Flags",
mapRestarting = "¡El mapa se reiniciará en %d segundos!",
chooseTip = "Escoge este personaje para jugar.",
deleteTip = "Eliminar este personaje.",
storageInUse = "¡Alguien ya está usando esto!",
storageSearching = "Buscando...",
container = "Contenedor",
containerPassword = "Has establecido la contraseña de este contenedor a %s.",
containerPasswordRemove = "Has eliminado la contraseña de este contenedor.",
containerPasswordWrite = "Introduce la contraseña.",
containerName = "Has cambiado el nombre de este contenedor a %s.",
containerNameWrite = "Introduce el nombre.",
containerNameRemove = "Has borrado el nombre de este contenedor.",
containerInvalid = "¡Necesitas estar mirando a un contenedor para hacer esto!",
passwordAttemptLimit = "¡Demasiados intentos fallidos de contraseña!",
wrongPassword = "Has introducido una contraseña errónea.",
respawning = "Reapareciendo...",
syntax = "Formato: %s",
tellAdmin = "Informa a un miembro del staff de este error: %s",
mapAdd = "Has añadido una escena de mapa.",
mapDel = "Has borrado %d escena(s) de mapa.",
mapRepeat = "Ahora añade el segundo punto.",
scoreboard = "Marcador",
ping = "Ping: %d",
viewProfile = "Ver el perfil de Steam.",
copySteamID = "Copiar Steam ID",
money = "Dinero",
moneyLeft = "Dinero restante: ",
currentMoney = "Dinero actual: ",
invalidClass = "¡Esa no es una clase válida!",
invalidClassFaction = "¡Esa no es una clase válida para la facción!",
miscellaneous = "Misceláneo",
general = "General",
observer = "Observador",
performance = "Rendimiento",
thirdperson = "Tercera Persona",
date = "Fecha",
interaction = "Interacción",
server = "Servidor",
resetDefault = "Restablecer a valores predeterminados",
resetDefaultDescription = "Esto restablecerá \"%s\" a su valor predeterminado de \"%s\".",
optOpenBags = "Abrir objetos con inventario",
optdOpenBags = "Abrir automáticamente todos los objetos con inventario cuando el menú es abierto.",
optShowIntro = "Mostrar la intro al entrar",
optdShowIntro = "Muestra la introducción de Helix la siguiente vez que entres. Esta opción siempre es deshabilitada una vez la has visto.",
optCheapBlur = "Deshabilitar difuminación",
optdCheapBlur = "Remplaza la difuminación de la interfaz con un oscurecido simple.",
optObserverTeleportBack = "Volver a la posición anterior",
optdObserverTeleportBack = "Te hace volver a la posición en la cual te encontrabas antes de entrar en modo observador.",
optObserverESP = "Mostrar admin ESP",
optdObserverESP = "Muestra los nombres y las localizaciones de cada jugador en el servidor.",
opt24hourTime = "Usar formato de 24 horas",
optd24hourTime = "Muestra las marcas de tiempo en formato 24 horas, en vez de usar el formato de 12 horas (AM/PM).",
optChatNotices = "Mostrar avisos en el chat",
optdChatNotices = "Pone todos los avisos que aparecen en la esquina superior derecha, en el chat.",
optChatTimestamps = "Mostrar marcas de tiempo en el chat",
optdChatTimestamps = "Prepone una marca temporal a cada mensaje en el chat.",
optAlwaysShowBars = "Mostrar siempre las barras de información",
optdAlwaysShowBars = "Siempre muestra las barras de información en la esquina superior izquierda, sin importar si deberían de mostrarse o no.",
optAltLower = "Ocultar las manos al estar bajadas", -- @todo remove me
optdAltLower = "Oculta tus manos cuando están bajadas.", -- @todo remove me
optThirdpersonEnabled = "Activar Tercera Persona",
optdThirdpersonEnabled = "Pone la cámara detrás de ti. Esto también puede ser activado con el comando de consola \"ix_togglethirdperson\".",
optThirdpersonClassic = "Activar el estilo clásico de tercera persona",
optdThirdpersonClassic = "Mueve la vista de tu personaje con tu ratón.",
optThirdpersonVertical = "Altura de Tercera Persona",
optdThirdpersonVertical = "Como de alto debería de estar la cámara de tercera persona.",
optThirdpersonHorizontal = "Horizontal de Tercera Persona",
optdThirdpersonHorizontal = "Como de lejos a la izquierda o la derecha debería de estar la cámara.",
optThirdpersonDistance = "Distancia de Tercera Persona",
optdThirdpersonDistance = "Como de lejos debería de estar la cámara.",
optThirdpersonCrouchOffset = "Altura de la cámara al agacharse",
optdThirdpersonCrouchOffset = "A que altura debe de estar la cámara cuando se está agachado.",
optDisableAnimations = "Desactivar animaciones",
optdDisableAnimations = "Elimina las animaciones de la interfaz de usuario, haciendo que las transiciones sean instantáneas.",
optAnimationScale = "Escala de animación",
optdAnimationScale = "Velocidad de reproducción de las animaciones de la interfaz de usuario.",
optLanguage = "Idioma",
optdLanguage = "El idioma mostrado en la interfaz de usuario de Helix.",
optMinimalTooltips = "Información del HUD minimalista",
optdMinimalTooltips = "Cambia el estilo de la información del HUD para que ocupe menos espacio.",
optNoticeDuration = "Duración de Avisos",
optdNoticeDuration = "Cuanto tiempo duran los avisos (en segundos).",
optNoticeMax = "Máximo de Avisos",
optdNoticeMax = "La cantidad de avisos mostrados antes de que sean eliminados los anteriores.",
optChatFontScale = "Tamaño de fuente del chat",
optdChatFontScale = "Modifica el tamaño de la fuente del chat.",
optChatOutline = "Contorno en el texto de chat",
optdChatOutline = "Dibuja un contorno alrededor del texto del chat, en vez de una sombra paralela. Activa esto si tienes problemas leyendo el texto.",
optEscCloseMenu = "Tecla ESC vuelve al juego",
optdEscCloseMenu = "Al pulsar la tecla ESC, se cierra el menú de Helix y también se oculta automáticamente el menú de pausa de Garry's Mod. Es decir, vuelves directamente a controlar tu personaje.",
cmdRoll = "Tira un numero entre 0 y el número especificado.",
cmdPM = "Envía un mensaje privado a alguien.",
cmdReply = "Responde a la ultima persona de la cual recibiste un mensaje privado.",
cmdSetVoicemail = "Establece o elimina el mensaje de respuesta automática cuando alguien te envía un mensaje privado.",
cmdCharGiveFlag = "Da la(s) flag(s) especificadas a alguien.",
cmdCharTakeFlag = "Elimina la(s) flag(s) especificada(s) de alguien si las tienen.",
cmdToggleRaise = "Levanta o baja el arma que estas que estas sosteniendo.",
cmdCharSetModel = "Establece el modelo del personaje de una persona.",
cmdCharSetSkin = "Establece la 'skin' del modelo de un personaje.",
cmdCharSetBodygroup = "Establece el 'bodygroup' especificado del modelo de un personaje.",
cmdCharSetAttribute = "Establece el nivel del atributo especificado para alguien.",
cmdCharAddAttribute = "Añade un nivel al atributo especificado para alguien.",
cmdCharSetName = "Cambia el nombre de un personaje al nombre especificado.",
cmdCharGiveItem = "Da el objeto especificado a alguien.",
cmdCharKick = "Saca al jugador de su personaje, devolviéndolo a la pantalla de selección.",
cmdCharBan = "Bloquea a un jugador el uso del personaje especificado.",
cmdCharUnban = "Desbloquea un personaje para que el jugador pueda usarlo otra vez.",
cmdGiveMoney = "Da una cantidad específica de dinero a la persona a la que estas mirando.",
cmdCharSetMoney = "Cambia la cantidad total de dinero de alguien a la cantidad especificada.",
cmdDropMoney = "Tira una cantidad específica de dinero en una pequeña caja en frente de ti.",
cmdPlyWhitelist = "Permite el acceso de alguien a la facción especificada (lista blanca).",
cmdCharGetUp = "Intenta levantarte después de haber caído.",
cmdPlyUnwhitelist = "Elimina el acceso de alguien a la facción especificada.",
cmdCharFallOver = "Hace que tus rodillas se debiliten y te caigas.",
cmdBecomeClass = "Intenta formar parte de la clase especificada en tu facción actual.",
cmdCharDesc = "Establece tu descripción física.",
cmdCharDescTitle = "Descripción Física",
cmdCharDescDescription = "Introduce la descripción física de tu personaje.",
cmdPlyTransfer = "Transfiere a alguien a la facción especificada.",
cmdCharSetClass = "Forzar a alguien formar parte de la clase especificada de su facción.",
cmdMapRestart = "Reinicia el mapa después de la cantidad de tiempo especificada.",
cmdPanelAdd = "Pone un panel web en el mundo.",
cmdPanelRemove = "Elimina el panel web al que estas mirando.",
cmdTextAdd = "Pone un bloque de texto en el mundo.",
cmdTextRemove = "Elimina bloques de texto de donde estas mirando.",
cmdMapSceneAdd = "Añade un punto de cámara cinemática la cual es mostrada en el menú de selección de personaje.",
cmdMapSceneRemove = "Elimina un punto de cámara que es mostrado en el menú de selección de personaje.",
cmdSpawnAdd = "Añade un punto de aparición para la facción especificada.",
cmdSpawnRemove = "Elimina cualquier punto de aparición a los cuales estés mirando.",
cmdAct = "Realiza la animación %s.",
cmdContainerSetPassword = "Establece la contraseña para el contenedor que estas mirando.",
cmdDoorSell = "Vende la puerta a la que estas mirando.",
cmdDoorBuy = "Compra la puerta a la que estas mirando.",
cmdDoorSetUnownable = "Hace que la puerta a la que estas mirando no pueda tener dueño.",
cmdDoorSetOwnable = "Hace que la puerta a la que estas mirando pueda tener dueño.",
cmdDoorSetFaction = "Hace que la puerta a la que estas mirando pertenezca a una facción.",
cmdDoorSetDisabled = "No permite que ningún comando sea ejecutado en la puerta a la cual estas mirando.",
cmdDoorSetTitle = "Establece el titulo de la puerta a la cual estas mirando.",
cmdDoorSetParent = "Selecciona la puerta como madre de un grupo de puertas.",
cmdDoorSetChild = "Establece la puerta como hija de un grupo de puertas.",
cmdDoorRemoveChild = "Elimina la puerta como hija del grupo de puertas seleccionado.",
cmdDoorSetHidden = "Oculta la descripción de la puerta a la cual estas mirando, pero sigue permitiendo que pueda tener dueño.",
cmdDoorSetClass = "Hace que la puerta a la que estas mirando pertenezca la clase especificada de una facción.",
cmdMe = "Describe una acción de tu personaje en tercera persona.",
cmdIt = "Describe un evento a tu alrededor.",
cmdW = "Susurra algo a las personas a tu alrededor.",
cmdY = "Grita algo a las personas a tu alrededor.",
cmdEvent = "Describe un evento que todos pueden ver en el servidor.",
cmdOOC = "Envía un mensaje en el chat (global) fuera de personaje.",
cmdLOOC = "Envía un mensaje en el chat (local) fuera de personaje.",
iconEditorAlignBest = "Alineación óptima",
iconEditorWidth = "Ancho",
iconEditorHeight = "Altura",
iconEditorCopy = "Copiar al portapapeles",
iconEditorCopied = "Datos del modelo del objeto copiados al portapapeles.",
iconEditorAlignFront = "Alinear desde el frente",
iconEditorAlignAbove = "Alinear desde arriba",
iconEditorAlignRight = "Alinear desde la derecha",
iconEditorAlignCenter = "Alinear desde el centro"
}
================================================
FILE: gamemode/shared.lua
================================================
--- Top-level library containing all Helix libraries. A large majority of the framework is split into respective libraries that
-- reside within `ix`.
-- @module ix
--- A table of variable types that are used throughout the framework. It represents types as a table with the keys being the
-- name of the type, and the values being some number value. **You should never directly use these number values!** Using the
-- values from this table will ensure backwards compatibility if the values in this table change.
--
-- This table also contains the numerical values of the types as keys. This means that if you need to check if a type exists, or
-- if you need to get the name of a type, you can do a table lookup with a numerical value. Note that special types are not
-- included since they are not real types that can be compared with.
-- @table ix.type
-- @realm shared
-- @field string A regular string. In the case of `ix.command.Add`, this represents one word.
-- @field text A regular string. In the case of `ix.command.Add`, this represents all words concatenated into a string.
-- @field number Any number.
-- @field player Any player that matches the given query string in `ix.util.FindPlayer`.
-- @field steamid A string that matches the Steam ID format of `STEAM_X:X:XXXXXXXX`.
-- @field character Any player's character that matches the given query string in `ix.util.FindPlayer`.
-- @field bool A string representation of a bool - `false` and `0` will return `false`, anything else will return `true`.
-- @field color A color represented by its red/green/blue/alpha values.
-- @field vector A 3D vector represented by its x/y/z values.
-- @field optional This is a special type that can be bitwise OR'd with any other type to make it optional. Currently only
-- supported in `ix.command.Add`.
-- @field array This is a special type that can be bitwise OR'd with any other type to make it an array of that type. Currently
-- only supported in `ix.option.Add`.
-- @see ix.command.Add
-- @see ix.option.Add
-- @usage -- checking if type exists
-- print(ix.type[2] != nil)
-- > true
--
-- -- getting name of type
-- print(ix.type[ix.type.string])
-- > "string"
ix.type = ix.type or {}
-- Define gamemode information.
GM.Name = "Helix"
GM.Author = "nebulous.cloud"
GM.Website = "https://nebulous.cloud"
GM.Version = "β"
do
-- luacheck: globals player_manager
player_manager.ixTranslateModel = player_manager.ixTranslateModel or player_manager.TranslateToPlayerModelName
function player_manager.TranslateToPlayerModelName(model)
model = model:lower():gsub("\\", "/")
local result = player_manager.ixTranslateModel(model)
if (result == "kleiner" and !model:find("kleiner")) then
local model2 = model:gsub("models/", "models/player/")
result = player_manager.ixTranslateModel(model2)
if (result != "kleiner") then
return result
end
model2 = model:gsub("models/humans", "models/player")
result = player_manager.ixTranslateModel(model2)
if (result != "kleiner") then
return result
end
model2 = model:gsub("models/zombie/", "models/player/zombie_")
result = player_manager.ixTranslateModel(model2)
if (result != "kleiner") then
return result
end
end
return result
end
end
-- Include core framework files.
ix.util.Include("core/cl_skin.lua")
ix.util.IncludeDir("core/libs/thirdparty")
ix.util.Include("core/sh_config.lua")
ix.util.IncludeDir("core/libs")
ix.util.IncludeDir("core/derma")
ix.util.IncludeDir("core/hooks")
-- Include language and default base items.
ix.lang.LoadFromDir("helix/gamemode/languages")
ix.item.LoadFromDir("helix/gamemode/items")
-- Called after the gamemode has loaded.
function GM:Initialize()
-- Load all of the Helix plugins.
ix.plugin.Initialize()
-- Restore client options
ix.option.Load()
-- Restore the configurations from earlier if applicable.
ix.config.Load()
end
-- luacheck: globals IX_RELOADED
IX_RELOADED = false
-- Called when a file has been modified.
function GM:OnReloaded()
-- Reload the default fonts.
if (CLIENT) then
hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont"))
-- Reload the scoreboard.
if (IsValid(ix.gui.scoreboard)) then
ix.gui.scoreboard:Remove()
end
else
-- Auto-reload support for faction pay timers.
for index, faction in ipairs(ix.faction.indices) do
for _, v in ipairs(team.GetPlayers(index)) do
if (faction.pay and faction.pay > 0) then
timer.Adjust("ixSalary"..v:SteamID64(), faction.payTime or 300, 0)
else
timer.Remove("ixSalary"..v:SteamID64())
end
end
end
end
if (!IX_RELOADED) then
IX_RELOADED = true
-- Load all of the Helix plugins.
ix.plugin.Initialize()
-- Restore the configurations from earlier if applicable.
ix.config.Load()
-- Restore client options
ix.option.Load()
end
end
-- Include default Helix chat commands.
ix.util.Include("core/sh_commands.lua")
if (SERVER and game.IsDedicated()) then
concommand.Remove("gm_save")
concommand.Add("gm_save", function(client, command, arguments) end)
concommand.Add("gmod_admin_cleanup", function(client, command, arguments) end)
end
-- add entries for c_viewmodels that aren't set by default
player_manager.AddValidModel("group02male01", "models/humans/group02/male_01.mdl")
player_manager.AddValidHands("group02male01", "models/weapons/c_arms_citizen.mdl", 1, "0000000")
player_manager.AddValidModel("group02male03", "models/humans/group02/male_03.mdl")
player_manager.AddValidHands("group02male03", "models/weapons/c_arms_citizen.mdl", 1, "0000000")
player_manager.AddValidModel("group01female07", "models/player/group01/female_07.mdl")
player_manager.AddValidHands("group01female07", "models/weapons/c_arms_citizen.mdl", 1, "0000000")
player_manager.AddValidModel("group02female03", "models/player/group01/female_03.mdl")
player_manager.AddValidHands("group02female03", "models/weapons/c_arms_citizen.mdl", 1, "0000000")
================================================
FILE: helix.example.yml
================================================
database:
adapter: "sqlite"
hostname: "example.com"
username: "example"
password: "example"
database: "helix"
port: 3306
================================================
FILE: helix.txt
================================================
"helix"
{
"base" "sandbox"
"title" "Helix"
"author" "nebulous.cloud"
}
================================================
FILE: plugins/3dpanel.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "3D Panels"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds web panels that can be placed on the map."
-- List of available panel dislays.
PLUGIN.list = PLUGIN.list or {}
if (SERVER) then
util.AddNetworkString("ixPanelList")
util.AddNetworkString("ixPanelAdd")
util.AddNetworkString("ixPanelRemove")
-- Called when the player is sending client info.
function PLUGIN:PlayerInitialSpawn(client)
-- Send the list of panel displays.
timer.Simple(1, function()
if (IsValid(client)) then
local json = util.TableToJSON(self.list)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixPanelList")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
-- Adds a panel to the list, sends it to the players, and saves data.
function PLUGIN:AddPanel(position, angles, url, scale, brightness)
scale = math.Clamp((scale or 1) * 0.1, 0.001, 5)
brightness = math.Clamp(math.Round((brightness or 100) * 2.55), 1, 255)
-- Find an ID for this panel within the list.
local index = #self.list + 1
-- Add the panel to the list so it can be sent and saved.
self.list[index] = {position, angles, nil, nil, scale, url, nil, brightness}
-- Send the panel information to the players.
net.Start("ixPanelAdd")
net.WriteUInt(index, 32)
net.WriteVector(position)
net.WriteAngle(angles)
net.WriteFloat(scale)
net.WriteString(url)
net.WriteUInt(brightness, 8)
net.Broadcast()
-- Save the plugin data.
self:SavePanels()
end
-- Removes a panel that are within the radius of a position.
function PLUGIN:RemovePanel(position, radius)
-- Default the radius to 100.
radius = radius or 100
local panelsDeleted = {}
-- Loop through all of the panels.
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
-- Check if the distance from our specified position to the panel is less than the radius.
if (v[1]:Distance(position) <= radius) then
panelsDeleted[#panelsDeleted + 1] = k
end
end
-- Save the plugin data if we actually changed anything.
if (#panelsDeleted > 0) then
-- Invert index table to delete from highest -> lowest
panelsDeleted = table.Reverse(panelsDeleted)
for _, v in ipairs(panelsDeleted) do
-- Remove the panel from the list of panels.
table.remove(self.list, v)
-- Tell the players to stop showing the panel.
net.Start("ixPanelRemove")
net.WriteUInt(v, 32)
net.Broadcast()
end
self:SavePanels()
end
-- Return the number of deleted panels.
return #panelsDeleted
end
-- Called after entities have been loaded on the map.
function PLUGIN:LoadData()
self.list = self:GetData() or {}
-- Formats table to sequential to support legacy panels.
self.list = table.ClearKeys(self.list)
end
-- Called when the plugin needs to save information.
function PLUGIN:SavePanels()
self:SetData(self.list)
end
else
-- Pre-define the zero index in client before the net receives
PLUGIN.list[0] = PLUGIN.list[0] or 0
-- Holds the current cached material and filename.
local cachedPreview = {}
local function CacheMaterial(index)
if (index < 1) then
return
end
local info = PLUGIN.list[index]
local exploded = string.Explode("/", info[6])
local filename = exploded[#exploded]
local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/"
if (file.Exists(path..filename, "DATA")) then
local material = Material("../data/"..path..filename, "noclamp smooth")
if (!material:IsError()) then
info[7] = material
-- Set width and height
info[3] = material:GetInt("$realwidth")
info[4] = material:GetInt("$realheight")
end
else
file.CreateDir(path)
http.Fetch(info[6], function(body)
file.Write(path..filename, body)
local material = Material("../data/"..path..filename, "noclamp smooth")
if (!material:IsError()) then
info[7] = material
-- Set width and height
info[3] = material:GetInt("$realwidth")
info[4] = material:GetInt("$realheight")
end
end)
end
end
local function UpdateCachedPreview(url)
local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/"
-- Gets the file name
local exploded = string.Explode("/", url)
local filename = exploded[#exploded]
if (file.Exists(path..filename, "DATA")) then
local preview = Material("../data/"..path..filename, "noclamp smooth")
-- Update the cached preview if success
if (!preview:IsError()) then
cachedPreview = {url, preview}
else
cachedPreview = {}
end
else
file.CreateDir(path)
http.Fetch(url, function(body)
file.Write(path..filename, body)
local preview = Material("../data/"..path..filename, "noclamp smooth")
-- Update the cached preview if success
if (!preview:IsError()) then
cachedPreview = {url, preview}
else
cachedPreview = {}
end
end)
end
end
-- Receives new panel objects that need to be drawn.
net.Receive("ixPanelAdd", function()
local index = net.ReadUInt(32)
local position = net.ReadVector()
local angles = net.ReadAngle()
local scale = net.ReadFloat()
local url = net.ReadString()
local brightness = net.ReadUInt(8)
if (url != "") then
PLUGIN.list[index] = {position, angles, nil, nil, scale, url, nil, brightness}
CacheMaterial(index)
PLUGIN.list[0] = #PLUGIN.list
end
end)
net.Receive("ixPanelRemove", function()
local index = net.ReadUInt(32)
table.remove(PLUGIN.list, index)
PLUGIN.list[0] = #PLUGIN.list
end)
-- Receives a full update on ALL panels.
net.Receive("ixPanelList", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress panel data!\n")
return
end
-- Set the list of panels to the ones provided by the server.
PLUGIN.list = util.JSONToTable(uncompressed)
-- Will be saved, but refresh just to make sure.
PLUGIN.list[0] = #PLUGIN.list
local CacheQueue = {}
-- Loop through the list of panels.
for k, _ in pairs(PLUGIN.list) do
if (k == 0) then
continue
end
CacheQueue[#CacheQueue + 1] = k
end
if (#CacheQueue == 0) then
return
end
timer.Create("ixCache3DPanels", 1, #CacheQueue, function()
if (#CacheQueue > 0) then
CacheMaterial(CacheQueue[1])
table.remove(CacheQueue, 1)
else
timer.Remove("ixCache3DPanels")
end
end)
end)
-- Called after all translucent objects are drawn.
function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox)
if (bDrawingDepth or bDrawingSkybox) then
return
end
-- Panel preview
if (ix.chat.currentCommand == "paneladd") then
self:PreviewPanel()
end
-- Store the position of the player to be more optimized.
local ourPosition = LocalPlayer():GetPos()
local panel = self.list
for i = 1, panel[0] do
local position = panel[i][1]
local image = panel[i][7]
-- Older panels do not have a brightness index
local brightness = panel[i][8] or 255
if (panel[i][7] and ourPosition:DistToSqr(position) <= 4194304) then
cam.Start3D2D(position, panel[i][2], panel[i][5] or 0.1)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
surface.SetDrawColor(brightness, brightness, brightness)
surface.SetMaterial(image)
surface.DrawTexturedRect(0, 0, panel[i][3] or image:Width(), panel[i][4] or image:Height())
render.PopFilterMag()
render.PopFilterMin()
cam.End3D2D()
end
end
end
function PLUGIN:ChatTextChanged(text)
if (ix.chat.currentCommand == "paneladd") then
-- Allow time for ix.chat.currentArguments to update
timer.Simple(0, function()
local arguments = ix.chat.currentArguments
if (!arguments[1]) then
return
end
UpdateCachedPreview(arguments[1])
end)
end
end
function PLUGIN:PreviewPanel()
local arguments = ix.chat.currentArguments
-- if there's no URL, then no preview.
if (!arguments[1]) then
return
end
-- If the material is valid, preview the panel
if (cachedPreview[2] and !cachedPreview[2]:IsError()) then
local trace = LocalPlayer():GetEyeTrace()
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
local position = (trace.HitPos + angles:Up() * 0.1)
local ourPosition = LocalPlayer():GetPos()
-- validate argument types
local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5)
local brightness = math.Clamp(math.Round((tonumber(arguments[3]) or 100) * 2.55), 1, 255)
-- Attempt to collect the dimensions from the Material
local width, height = cachedPreview[2]:GetInt("$realwidth"), cachedPreview[2]:GetInt("$realheight")
if (ourPosition:DistToSqr(position) <= 4194304) then
cam.Start3D2D(position, angles, scale or 0.1)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
surface.SetDrawColor(brightness, brightness, brightness)
surface.SetMaterial(cachedPreview[2])
surface.DrawTexturedRect(0, 0, width or cachedPreview[2]:Width(), height or cachedPreview[2]:Height())
render.PopFilterMag()
render.PopFilterMin()
cam.End3D2D()
end
end
end
end
ix.command.Add("PanelAdd", {
description = "@cmdPanelAdd",
privilege = "Manage Panels",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.number, ix.type.optional),
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, url, scale, brightness)
-- Get the position and angles of the panel.
local trace = client:GetEyeTrace()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
-- Add the panel.
PLUGIN:AddPanel(position + angles:Up() * 0.1, angles, url, scale, brightness)
return "@panelAdded"
end
})
ix.command.Add("PanelRemove", {
description = "@cmdPanelRemove",
privilege = "Manage Panels",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
-- Get the origin to remove panel.
local trace = client:GetEyeTrace()
local position = trace.HitPos
-- Remove the panel(s) and get the amount removed.
local amount = PLUGIN:RemovePanel(position, radius)
return "@panelRemoved", amount
end
})
================================================
FILE: plugins/3dtext.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "3D Text"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds text that can be placed on the map."
-- List of available text panels
PLUGIN.list = PLUGIN.list or {}
if (SERVER) then
util.AddNetworkString("ixTextList")
util.AddNetworkString("ixTextAdd")
util.AddNetworkString("ixTextRemove")
ix.log.AddType("undo3dText", function(client)
return string.format("%s has removed their last 3D text.", client:GetName())
end)
-- Called when the player is sending client info.
function PLUGIN:PlayerInitialSpawn(client)
timer.Simple(1, function()
if (IsValid(client)) then
local json = util.TableToJSON(self.list)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixTextList")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
-- Adds a text to the list, sends it to the players, and saves data.
function PLUGIN:AddText(position, angles, text, scale)
local index = #self.list + 1
scale = math.Clamp((scale or 1) * 0.1, 0.001, 5)
self.list[index] = {position, angles, text, scale}
net.Start("ixTextAdd")
net.WriteUInt(index, 32)
net.WriteVector(position)
net.WriteAngle(angles)
net.WriteString(text)
net.WriteFloat(scale)
net.Broadcast()
self:SaveText()
return index
end
-- Removes a text that are within the radius of a position.
function PLUGIN:RemoveText(position, radius)
radius = radius or 100
local textDeleted = {}
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
if (v[1]:Distance(position) <= radius) then
textDeleted[#textDeleted + 1] = k
end
end
if (#textDeleted > 0) then
-- Invert index table to delete from highest -> lowest
textDeleted = table.Reverse(textDeleted)
for _, v in ipairs(textDeleted) do
table.remove(self.list, v)
net.Start("ixTextRemove")
net.WriteUInt(v, 32)
net.Broadcast()
end
self:SaveText()
end
return #textDeleted
end
function PLUGIN:RemoveTextByID(id)
local info = self.list[id]
if (!info) then
return false
end
net.Start("ixTextRemove")
net.WriteUInt(id, 32)
net.Broadcast()
table.remove(self.list, id)
return true
end
-- Called after entities have been loaded on the map.
function PLUGIN:LoadData()
self.list = self:GetData() or {}
-- Formats table to sequential to support legacy panels.
self.list = table.ClearKeys(self.list)
end
-- Called when the plugin needs to save information.
function PLUGIN:SaveText()
self:SetData(self.list)
end
else
-- Pre-define the zero index in client before the net receives
PLUGIN.list[0] = PLUGIN.list[0] or 0
language.Add("Undone_ix3dText", "Removed 3D Text")
function PLUGIN:GenerateMarkup(text)
local object = ix.markup.Parse(""..text:gsub("\\n", "\n"))
object.onDrawText = function(surfaceText, font, x, y, color, alignX, alignY, alpha)
-- shadow
surface.SetTextPos(x + 1, y + 1)
surface.SetTextColor(0, 0, 0, alpha)
surface.SetFont(font)
surface.DrawText(surfaceText)
surface.SetTextPos(x, y)
surface.SetTextColor(color.r or 255, color.g or 255, color.b or 255, alpha)
surface.SetFont(font)
surface.DrawText(surfaceText)
end
return object
end
-- Receives new text objects that need to be drawn.
net.Receive("ixTextAdd", function()
local index = net.ReadUInt(32)
local position = net.ReadVector()
local angles = net.ReadAngle()
local text = net.ReadString()
local scale = net.ReadFloat()
if (text != "") then
PLUGIN.list[index] = {
position,
angles,
PLUGIN:GenerateMarkup(text),
scale
}
PLUGIN.list[0] = #PLUGIN.list
end
end)
net.Receive("ixTextRemove", function()
local index = net.ReadUInt(32)
table.remove(PLUGIN.list, index)
PLUGIN.list[0] = #PLUGIN.list
end)
-- Receives a full update on ALL texts.
net.Receive("ixTextList", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress text data!\n")
return
end
PLUGIN.list = util.JSONToTable(uncompressed)
-- Will be saved, but refresh just to make sure.
PLUGIN.list[0] = #PLUGIN.list
for k, v in pairs(PLUGIN.list) do
if (k == 0) then
continue
end
local object = ix.markup.Parse(""..v[3]:gsub("\\n", "\n"))
object.onDrawText = function(text, font, x, y, color, alignX, alignY, alpha)
draw.TextShadow({
pos = {x, y},
color = ColorAlpha(color, alpha),
text = text,
xalign = 0,
yalign = alignY,
font = font
}, 1, alpha)
end
v[3] = object
end
end)
function PLUGIN:StartChat()
self.preview = nil
end
function PLUGIN:FinishChat()
self.preview = nil
end
function PLUGIN:HUDPaint()
if (ix.chat.currentCommand != "textremove") then
return
end
local radius = tonumber(ix.chat.currentArguments[1]) or 100
surface.SetDrawColor(200, 30, 30)
surface.SetTextColor(200, 30, 30)
surface.SetFont("ixMenuButtonFont")
local i = 0
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
if (v[1]:Distance(LocalPlayer():GetEyeTraceNoCursor().HitPos) <= radius) then
local screen = v[1]:ToScreen()
surface.DrawLine(
ScrW() * 0.5,
ScrH() * 0.5,
math.Clamp(screen.x, 0, ScrW()),
math.Clamp(screen.y, 0, ScrH())
)
i = i + 1
end
end
if (i > 0) then
local textWidth, textHeight = surface.GetTextSize(i)
surface.SetTextPos(ScrW() * 0.5 - textWidth * 0.5, ScrH() * 0.5 + textHeight + 8)
surface.DrawText(i)
end
end
function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox)
if (bDrawingDepth or bDrawingSkybox) then
return
end
-- preview for textadd command
if (ix.chat.currentCommand == "textadd") then
local arguments = ix.chat.currentArguments
local text = tostring(arguments[1] or "")
local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5)
local trace = LocalPlayer():GetEyeTraceNoCursor()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
local markup
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
-- markup will error with invalid fonts
pcall(function()
markup = PLUGIN:GenerateMarkup(text)
end)
if (markup) then
cam.Start3D2D(position, angles, scale)
markup:draw(0, 0, 1, 1, 255)
cam.End3D2D()
end
end
local position = LocalPlayer():GetPos()
local texts = self.list
for i = 1, texts[0] do
local distance = texts[i][1]:DistToSqr(position)
if (distance > 1048576) then
continue
end
cam.Start3D2D(texts[i][1], texts[i][2], texts[i][4] or 0.1)
local alpha = (1 - ((distance - 65536) / 768432)) * 255
texts[i][3]:draw(0, 0, 1, 1, alpha)
cam.End3D2D()
end
end
end
ix.command.Add("TextAdd", {
description = "@cmdTextAdd",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, text, scale)
local trace = client:GetEyeTrace()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
local index = PLUGIN:AddText(position + angles:Up() * 0.1, angles, text, scale)
undo.Create("ix3dText")
undo.SetPlayer(client)
undo.AddFunction(function()
if (PLUGIN:RemoveTextByID(index)) then
ix.log.Add(client, "undo3dText")
end
end)
undo.Finish()
return "@textAdded"
end
})
ix.command.Add("TextRemove", {
description = "@cmdTextRemove",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
local trace = client:GetEyeTrace()
local position = trace.HitPos + trace.HitNormal * 2
local amount = PLUGIN:RemoveText(position, radius)
return "@textRemoved", amount
end
})
================================================
FILE: plugins/act/cl_hooks.lua
================================================
local animationTime = 2
local PLUGIN = PLUGIN
PLUGIN.cameraFraction = 0
local function GetHeadBone(client)
local head
for i = 1, client:GetBoneCount() do
local name = client:GetBoneName(i)
if (string.find(name:lower(), "head")) then
head = i
break
end
end
return head
end
function PLUGIN:PlayerBindPress(client, bind, bPressed)
if (!client:GetNetVar("actEnterAngle")) then
return
end
if (bind:find("+jump") and bPressed) then
ix.command.Send("ExitAct")
return true
end
end
function PLUGIN:ShouldDrawLocalPlayer(client)
if (client:GetNetVar("actEnterAngle") and self.cameraFraction > 0.25) then
return true
elseif (self.cameraFraction > 0.25) then
return true
end
end
local forwardOffset = 16
local backwardOffset = -32
local heightOffset = Vector(0, 0, 20)
local idleHeightOffset = Vector(0, 0, 6)
local traceMin = Vector(-4, -4, -4)
local traceMax = Vector(4, 4, 4)
function PLUGIN:CalcView(client, origin)
local enterAngle = client:GetNetVar("actEnterAngle")
local fraction = self.cameraFraction
local offset = self.bIdle and forwardOffset or backwardOffset
local height = self.bIdle and idleHeightOffset or heightOffset
if (!enterAngle) then
if (fraction > 0) then
local view = {
origin = LerpVector(fraction, origin, origin + self.forward * offset + height)
}
if (self.cameraTween) then
self.cameraTween:update(FrameTime())
end
return view
end
return
end
local view = {}
local forward = enterAngle:Forward()
local head = GetHeadBone(client)
local bFirstPerson = true
if (ix.option.Get("thirdpersonEnabled", false)) then
local originPosition = head and client:GetBonePosition(head) or client:GetPos()
-- check if the camera will hit something
local data = util.TraceHull({
start = originPosition,
endpos = originPosition - client:EyeAngles():Forward() * 48,
mins = traceMin * 0.75,
maxs = traceMax * 0.75,
filter = client
})
bFirstPerson = data.Hit
if (!bFirstPerson) then
view.origin = data.HitPos
end
end
if (bFirstPerson) then
if (head) then
local position = client:GetBonePosition(head) + forward * offset + height
local data = {
start = (client:GetBonePosition(head) or Vector(0, 0, 64)) + forward * 8,
endpos = position + forward * offset,
mins = traceMin,
maxs = traceMax,
filter = client
}
data = util.TraceHull(data)
if (data.Hit) then
view.origin = data.HitPos
else
view.origin = position
end
else
view.origin = origin + forward * forwardOffset + height
end
end
view.origin = LerpVector(fraction, origin, view.origin)
if (self.cameraTween) then
self.cameraTween:update(FrameTime())
end
return view
end
net.Receive("ixActEnter", function()
PLUGIN.bIdle = net.ReadBool()
PLUGIN.forward = LocalPlayer():GetNetVar("actEnterAngle"):Forward()
PLUGIN.cameraTween = ix.tween.new(animationTime, PLUGIN, {
cameraFraction = 1
}, "outQuint")
end)
net.Receive("ixActLeave", function()
PLUGIN.cameraTween = ix.tween.new(animationTime * 0.5, PLUGIN, {
cameraFraction = 0
}, "outQuint")
end)
================================================
FILE: plugins/act/sh_definitions.lua
================================================
local function FacingWall(client)
local data = {}
data.start = client:EyePos()
data.endpos = data.start + client:GetForward() * 20
data.filter = client
if (!util.TraceLine(data).Hit) then
return "@faceWall"
end
end
local function FacingWallBack(client)
local data = {}
data.start = client:LocalToWorld(client:OBBCenter())
data.endpos = data.start - client:GetForward() * 20
data.filter = client
if (!util.TraceLine(data).Hit) then
return "@faceWallBack"
end
end
function PLUGIN:SetupActs()
-- sit
ix.act.Register("Sit", {"citizen_male", "citizen_female"}, {
start = {"idle_to_sit_ground", "idle_to_sit_chair"},
sequence = {"sit_ground", "sit_chair"},
finish = {
{"sit_ground_to_idle", duration = 2.1},
""
},
untimed = true,
idle = true
})
ix.act.Register("SitWall", {"citizen_male", "citizen_female"}, {
sequence = {
{"plazaidle4", check = FacingWallBack},
{"injured1", check = FacingWallBack, offset = function(client)
return client:GetForward() * 14
end}
},
untimed = true,
idle = true
})
ix.act.Register("Sit", "vortigaunt", {
sequence = "chess_wait",
untimed = true,
idle = true
})
-- stand
ix.act.Register("Stand", "citizen_male", {
sequence = {"lineidle01", "lineidle02", "lineidle03", "lineidle04"},
untimed = true,
idle = true
})
ix.act.Register("Stand", "citizen_female", {
sequence = {"lineidle01", "lineidle02", "lineidle03"},
untimed = true,
idle = true
})
ix.act.Register("Stand", "metrocop", {
sequence = "plazathreat2"
})
-- cheer
ix.act.Register("Cheer", "citizen_male", {
sequence = {{"cheer1", duration = 1.6}, "cheer2", "wave_smg1"}
})
ix.act.Register("Cheer", "citizen_female", {
sequence = {"cheer1", "wave_smg1"}
})
-- lean
ix.act.Register("Lean", {"citizen_male", "citizen_female"}, {
start = {"idle_to_lean_back", "", ""},
sequence = {
{"lean_back", check = FacingWallBack},
{"plazaidle1", check = FacingWallBack},
{"plazaidle2", check = FacingWallBack}
},
untimed = true,
idle = true
})
ix.act.Register("Lean", {"metrocop"}, {
sequence = {{"idle_baton", check = FacingWallBack}, "busyidle2"},
untimed = true,
idle = true
})
-- injured
ix.act.Register("Injured", "citizen_male", {
sequence = {"d1_town05_wounded_idle_1", "d1_town05_wounded_idle_2", "d1_town05_winston_down"},
untimed = true,
idle = true
})
ix.act.Register("Injured", "citizen_female", {
sequence = "d1_town05_wounded_idle_1",
untimed = true,
idle = true
})
-- arrest
ix.act.Register("ArrestWall", "citizen_male", {
sequence = {
{"apcarrestidle",
check = FacingWall,
offset = function(client)
return -client:GetForward() * 23
end},
"spreadwallidle"
},
untimed = true
})
ix.act.Register("Arrest", "citizen_male", {
sequence = "arrestidle",
untimed = true
})
-- threat
ix.act.Register("Threat", "metrocop", {
sequence = "plazathreat1",
})
-- deny
ix.act.Register("Deny", "metrocop", {
sequence = "harassfront2",
})
-- motion
ix.act.Register("Motion", "metrocop", {
sequence = {"motionleft", "motionright", "luggagewarn"}
})
-- wave
ix.act.Register("Wave", {"citizen_male", "citizen_female"}, {
sequence = {{"wave", duration = 2.75}, {"wave_close", duration = 1.75}}
})
-- pant
ix.act.Register("Pant", {"citizen_male", "citizen_female"}, {
start = {"d2_coast03_postbattle_idle02_entry", "d2_coast03_postbattle_idle01_entry"},
sequence = {"d2_coast03_postbattle_idle02", {"d2_coast03_postbattle_idle01", check = FacingWall}},
untimed = true
})
-- window
ix.act.Register("Window", "citizen_male", {
sequence = "d1_t03_tenements_look_out_window_idle",
untimed = true
})
ix.act.Register("Window", "citizen_female", {
sequence = "d1_t03_lookoutwindow",
untimed = true
})
end
================================================
FILE: plugins/act/sh_plugin.lua
================================================
--[[--
Provides players the ability to perform animations.
]]
-- @module ix.act
local PLUGIN = PLUGIN
PLUGIN.name = "Player Acts"
PLUGIN.description = "Adds animations that can be performed by certain models."
PLUGIN.author = "`impulse"
ix.act = ix.act or {}
ix.act.stored = ix.act.stored or {}
CAMI.RegisterPrivilege({
Name = "Helix - Player Acts",
MinAccess = "user"
})
--- Registers a sequence as a performable animation.
-- @realm shared
-- @string name Name of the animation (in CamelCase)
-- @string modelClass Model class to add this animation to
-- @tab data An `ActInfoStructure` table describing the animation
function ix.act.Register(name, modelClass, data)
ix.act.stored[name] = ix.act.stored[name] or {} -- might be adding onto an existing act
if (!data.sequence) then
return ErrorNoHalt(string.format(
"Act '%s' for '%s' tried to register without a provided sequence\n", name, modelClass
))
end
if (!istable(data.sequence)) then
data.sequence = {data.sequence}
end
if (data.start and istable(data.start) and #data.start != #data.sequence) then
return ErrorNoHalt(string.format(
"Act '%s' tried to register without matching number of enter sequences\n", name
))
end
if (data.finish and istable(data.finish) and #data.finish != #data.sequence) then
return ErrorNoHalt(string.format(
"Act '%s' tried to register without matching number of exit sequences\n", name
))
end
if (istable(modelClass)) then
for _, v in ipairs(modelClass) do
ix.act.stored[name][v] = data
end
else
ix.act.stored[name][modelClass] = data
end
end
--- Removes a sequence from being performable if it has been previously registered.
-- @realm shared
-- @string name Name of the animation
function ix.act.Remove(name)
ix.act.stored[name] = nil
ix.command.list["Act" .. name] = nil
end
ix.util.Include("sh_definitions.lua")
ix.util.Include("sv_hooks.lua")
ix.util.Include("cl_hooks.lua")
function PLUGIN:InitializedPlugins()
hook.Run("SetupActs")
hook.Run("PostSetupActs")
end
function PLUGIN:ExitAct(client)
client.ixUntimedSequence = nil
client:SetNetVar("actEnterAngle")
net.Start("ixActLeave")
net.Send(client)
end
function PLUGIN:PostSetupActs()
-- create chat commands for all stored acts
for act, classes in pairs(ix.act.stored) do
local variants = 1
local COMMAND = {
privilege = "Player Acts"
}
-- check if this act has any variants (i.e /ActSit 2)
for _, v in pairs(classes) do
if (#v.sequence > 1) then
variants = math.max(variants, #v.sequence)
end
end
-- setup command arguments if there are variants for this act
if (variants > 1) then
COMMAND.arguments = bit.bor(ix.type.number, ix.type.optional)
COMMAND.argumentNames = {"variant (1-" .. variants .. ")"}
end
COMMAND.GetDescription = function(command)
return L("cmdAct", act)
end
local privilege = "Helix - " .. COMMAND.privilege
-- we'll perform a model class check in OnCheckAccess to prevent the command from showing up on the client at all
COMMAND.OnCheckAccess = function(command, client)
local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil)
if (!bHasAccess) then
return false
end
local modelClass = ix.anim.GetModelClass(client:GetModel())
if (!classes[modelClass]) then
return false, "modelNoSeq"
end
return true
end
COMMAND.OnRun = function(command, client, variant)
variant = math.Clamp(tonumber(variant) or 1, 1, variants)
if (client:GetNetVar("actEnterAngle")) then
return "@notNow"
end
local modelClass = ix.anim.GetModelClass(client:GetModel())
local bCanEnter, error = PLUGIN:CanPlayerEnterAct(client, modelClass, variant, classes)
if (!bCanEnter) then
return error
end
local data = classes[modelClass]
local mainSequence = data.sequence[variant]
local mainDuration
-- check if the main sequence has any extra info
if (istable(mainSequence)) then
-- any validity checks to perform (i.e facing a wall)
if (mainSequence.check) then
local result = mainSequence.check(client)
if (result) then
return result
end
end
-- position offset
if (mainSequence.offset) then
client.ixOldPosition = client:GetPos()
client:SetPos(client:GetPos() + mainSequence.offset(client))
end
mainDuration = mainSequence.duration
mainSequence = mainSequence[1]
end
local startSequence = data.start and data.start[variant] or ""
local startDuration
if (istable(startSequence)) then
startDuration = startSequence.duration
startSequence = startSequence[1]
end
client:SetNetVar("actEnterAngle", client:GetAngles())
client:ForceSequence(startSequence, function()
-- we've finished the start sequence
client.ixUntimedSequence = data.untimed -- client can exit after the start sequence finishes playing
local duration = client:ForceSequence(mainSequence, function()
-- we've stopped playing the main sequence (either duration expired or user cancelled the act)
if (data.finish) then
local finishSequence = data.finish[variant]
local finishDuration
if (istable(finishSequence)) then
finishDuration = finishSequence.duration
finishSequence = finishSequence[1]
end
client:ForceSequence(finishSequence, function()
-- client has finished the end sequence and is no longer playing any animations
self:ExitAct(client)
end, finishDuration)
else
-- there's no end sequence so we can exit right away
self:ExitAct(client)
end
end, data.untimed and 0 or (mainDuration or nil))
if (!duration) then
-- the model doesn't support this variant
self:ExitAct(client)
client:NotifyLocalized("modelNoSeq")
return
end
end, startDuration, nil)
net.Start("ixActEnter")
net.WriteBool(data.idle or false)
net.Send(client)
client.ixNextAct = CurTime() + 4
end
ix.command.Add("Act" .. act, COMMAND)
end
-- setup exit act command
local COMMAND = {
privilege = "Player Acts",
OnRun = function(command, client)
if (client.ixUntimedSequence) then
client:LeaveSequence()
end
end
}
if (CLIENT) then
-- hide this command from the command list
COMMAND.OnCheckAccess = function(client)
return false
end
end
ix.command.Add("ExitAct", COMMAND)
end
function PLUGIN:UpdateAnimation(client, moveData)
local angle = client:GetNetVar("actEnterAngle")
if (angle) then
client:SetRenderAngles(angle)
end
end
do
local keyBlacklist = IN_ATTACK + IN_ATTACK2
function PLUGIN:StartCommand(client, command)
if (client:GetNetVar("actEnterAngle")) then
command:RemoveKey(keyBlacklist)
end
end
end
================================================
FILE: plugins/act/sv_hooks.lua
================================================
local PLUGIN = PLUGIN
util.AddNetworkString("ixActEnter")
util.AddNetworkString("ixActLeave")
function PLUGIN:CanPlayerEnterAct(client, modelClass, variant, act)
if (!client:Alive() or client:GetLocalVar("ragdoll") or client:WaterLevel() > 0 or !client:IsOnGround()) then
return false, L("notNow", client)
end
-- check if player's model class has an entry in this act table
modelClass = modelClass or ix.anim.GetModelClass(client:GetModel())
local data = act[modelClass]
if (!data) then
return false, L("modelNoSeq", client)
end
-- some models don't support certain variants
local sequence = data.sequence[variant]
if (!sequence) then
return false, L("modelNoSeq", client)
end
return true
end
function PLUGIN:PlayerDeath(client)
if (client.ixUntimedSequence) then
client:SetNetVar("actEnterAngle")
client:LeaveSequence()
client.ixUntimedSequence = nil
end
end
function PLUGIN:PlayerSpawn(client)
if (client.ixUntimedSequence) then
client:SetNetVar("actEnterAngle")
client:LeaveSequence()
client.ixUntimedSequence = nil
end
end
function PLUGIN:OnCharacterFallover(client)
if (client.ixUntimedSequence) then
client:SetNetVar("actEnterAngle")
client:LeaveSequence()
client.ixUntimedSequence = nil
end
end
================================================
FILE: plugins/ammosave.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Ammo Saver"
PLUGIN.author = "Black Tea"
PLUGIN.description = "Saves the ammo of a character."
PLUGIN.ammoList = {}
ix.ammo = ix.ammo or {}
function ix.ammo.Register(name)
name = name:lower()
if (!table.HasValue(PLUGIN.ammoList, name)) then
PLUGIN.ammoList[#PLUGIN.ammoList + 1] = name
end
end
-- Register Default HL2 Ammunition.
ix.ammo.Register("ar2")
ix.ammo.Register("pistol")
ix.ammo.Register("357")
ix.ammo.Register("smg1")
ix.ammo.Register("xbowbolt")
ix.ammo.Register("buckshot")
ix.ammo.Register("rpg_round")
ix.ammo.Register("smg1_grenade")
ix.ammo.Register("grenade")
ix.ammo.Register("ar2altfire")
ix.ammo.Register("slam")
-- Register Cut HL2 Ammunition.
ix.ammo.Register("alyxgun")
ix.ammo.Register("sniperround")
ix.ammo.Register("sniperpenetratedround")
ix.ammo.Register("thumper")
ix.ammo.Register("gravity")
ix.ammo.Register("battery")
ix.ammo.Register("gaussenergy")
ix.ammo.Register("combinecannon")
ix.ammo.Register("airboatgun")
ix.ammo.Register("striderminigun")
ix.ammo.Register("helicoptergun")
-- Called right before the character has its information save.
function PLUGIN:CharacterPreSave(character)
-- Get the player from the character.
local client = character:GetPlayer()
-- Check to see if we can get the player's ammo.
if (IsValid(client)) then
local ammoTable = {}
for _, v in ipairs(self.ammoList) do
local ammo = client:GetAmmoCount(v)
if (ammo > 0) then
ammoTable[v] = ammo
end
end
character:SetData("ammo", ammoTable)
end
end
-- Called after the player's loadout has been set.
function PLUGIN:PlayerLoadedCharacter(client)
timer.Simple(0.25, function()
if (!IsValid(client)) then
return
end
-- Get the saved ammo table from the character data.
local character = client:GetCharacter()
if (!character) then
return
end
local ammoTable = character:GetData("ammo")
-- Check if the ammotable is exists.
if (ammoTable) then
for k, v in pairs(ammoTable) do
client:SetAmmo(v, tostring(k))
end
end
end)
end
================================================
FILE: plugins/area/cl_hooks.lua
================================================
local PLUGIN = PLUGIN
local function DrawTextBackground(x, y, text, font, backgroundColor, padding)
font = font or "ixSubTitleFont"
padding = padding or 8
backgroundColor = backgroundColor or Color(88, 88, 88, 255)
surface.SetFont(font)
local textWidth, textHeight = surface.GetTextSize(text)
local width, height = textWidth + padding * 2, textHeight + padding * 2
ix.util.DrawBlurAt(x, y, width, height)
surface.SetDrawColor(0, 0, 0, 40)
surface.DrawRect(x, y, width, height)
derma.SkinFunc("DrawImportantBackground", x, y, width, height, backgroundColor)
surface.SetTextColor(color_white)
surface.SetTextPos(x + padding, y + padding)
surface.DrawText(text)
return height
end
function PLUGIN:InitPostEntity()
hook.Run("SetupAreaProperties")
end
function PLUGIN:ChatboxCreated()
if (IsValid(self.panel)) then
self.panel:Remove()
end
self.panel = vgui.Create("ixArea")
end
function PLUGIN:ChatboxPositionChanged(x, y, width, height)
if (!IsValid(self.panel)) then
return
end
self.panel:SetSize(width, y)
self.panel:SetPos(32, 0)
end
function PLUGIN:ShouldDrawCrosshair()
if (ix.area.bEditing) then
return true
end
end
function PLUGIN:PlayerBindPress(client, bind, bPressed)
if (!ix.area.bEditing) then
return
end
if ((bind:find("invnext") or bind:find("invprev")) and bPressed) then
return true
elseif (bind:find("attack2") and bPressed) then
self:EditRightClick()
return true
elseif (bind:find("attack") and bPressed) then
self:EditClick()
return true
elseif (bind:find("reload") and bPressed) then
self:EditReload()
return true
end
end
function PLUGIN:HUDPaint()
if (!ix.area.bEditing) then
return
end
local id = LocalPlayer():GetArea()
local area = ix.area.stored[id]
local height = ScrH()
local y = 64
y = y + DrawTextBackground(64, y, L("areaEditMode"), nil, ix.config.Get("color"))
if (!self.editStart) then
y = y + DrawTextBackground(64, y, L("areaEditTip"), "ixSmallTitleFont")
DrawTextBackground(64, y, L("areaRemoveTip"), "ixSmallTitleFont")
else
DrawTextBackground(64, y, L("areaFinishTip"), "ixSmallTitleFont")
end
if (area) then
DrawTextBackground(64, height - 64 - ScreenScale(12), id, "ixSmallTitleFont", area.properties.color)
end
end
function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox)
if (bSkybox or !ix.area.bEditing) then
return
end
-- draw all areas
for k, v in pairs(ix.area.stored) do
local center, min, max = self:GetLocalAreaPosition(v.startPosition, v.endPosition)
local color = ColorAlpha(v.properties.color or ix.config.Get("color"), 255)
render.DrawWireframeBox(center, angle_zero, min, max, color)
cam.Start2D()
local centerScreen = center:ToScreen()
local _, textHeight = draw.SimpleText(
k, "BudgetLabel", centerScreen.x, centerScreen.y, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if (v.type != "area") then
draw.SimpleText(
"(" .. L(v.type) .. ")", "BudgetLabel",
centerScreen.x, centerScreen.y + textHeight, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER
)
end
cam.End2D()
end
-- draw currently edited area
if (self.editStart) then
local center, min, max = self:GetLocalAreaPosition(self.editStart, self:GetPlayerAreaTrace().HitPos)
local color = Color(255, 255, 255, 25 + (1 + math.sin(SysTime() * 6)) * 115)
render.DrawWireframeBox(center, angle_zero, min, max, color)
cam.Start2D()
local centerScreen = center:ToScreen()
draw.SimpleText(L("areaNew"), "BudgetLabel",
centerScreen.x, centerScreen.y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
cam.End2D()
end
end
function PLUGIN:EditRightClick()
if (self.editStart) then
self.editStart = nil
else
self:StopEditing()
end
end
function PLUGIN:EditClick()
if (!self.editStart) then
self.editStart = LocalPlayer():GetEyeTraceNoCursor().HitPos
elseif (self.editStart and !self.editProperties) then
self.editProperties = true
local panel = vgui.Create("ixAreaEdit")
panel:MakePopup()
end
end
function PLUGIN:EditReload()
if (self.editStart) then
return
end
local id = LocalPlayer():GetArea()
local area = ix.area.stored[id]
if (!area) then
return
end
Derma_Query(L("areaDeleteConfirm", id), L("areaDelete"),
L("no"), nil,
L("yes"), function()
net.Start("ixAreaRemove")
net.WriteString(id)
net.SendToServer()
end
)
end
function PLUGIN:ShouldDisplayArea(id)
if (ix.area.bEditing) then
return false
end
end
function PLUGIN:OnAreaChanged(oldID, newID)
local client = LocalPlayer()
client.ixArea = newID
local area = ix.area.stored[newID]
if (!area) then
client.ixInArea = false
return
end
client.ixInArea = true
if (hook.Run("ShouldDisplayArea", newID) == false or !area.properties.display) then
return
end
local format = newID .. (ix.option.Get("24hourTime", false) and ", %H:%M." or ", %I:%M %p.")
format = ix.date.GetFormatted(format)
self.panel:AddEntry(format, area.properties.color)
end
net.Receive("ixAreaEditStart", function()
PLUGIN:StartEditing()
end)
net.Receive("ixAreaEditEnd", function()
PLUGIN:StopEditing()
end)
net.Receive("ixAreaAdd", function()
local name = net.ReadString()
local type = net.ReadString()
local startPosition, endPosition = net.ReadVector(), net.ReadVector()
local properties = net.ReadTable()
if (name != "") then
ix.area.stored[name] = {
type = type,
startPosition = startPosition,
endPosition = endPosition,
properties = properties
}
end
end)
net.Receive("ixAreaRemove", function()
local name = net.ReadString()
if (ix.area.stored[name]) then
ix.area.stored[name] = nil
end
end)
net.Receive("ixAreaSync", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress area data!\n")
return
end
-- Set the list of texts to the ones provided by the server.
ix.area.stored = util.JSONToTable(uncompressed)
end)
net.Receive("ixAreaChanged", function()
local oldID, newID = net.ReadString(), net.ReadString()
hook.Run("OnAreaChanged", oldID, newID)
end)
================================================
FILE: plugins/area/cl_plugin.lua
================================================
local PLUGIN = PLUGIN
function PLUGIN:GetPlayerAreaTrace()
local client = LocalPlayer()
return util.TraceLine({
start = client:GetShootPos(),
endpos = client:GetShootPos() + client:GetForward() * 96,
filter = client
})
end
function PLUGIN:StartEditing()
ix.area.bEditing = true
self.editStart = nil
self.editProperties = nil
end
function PLUGIN:StopEditing()
ix.area.bEditing = false
if (IsValid(ix.gui.areaEdit)) then
ix.gui.areaEdit:Remove()
end
end
================================================
FILE: plugins/area/derma/cl_area.lua
================================================
-- area entry
DEFINE_BASECLASS("Panel")
local PANEL = {}
AccessorFunc(PANEL, "text", "Text", FORCE_STRING)
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
AccessorFunc(PANEL, "tickSound", "TickSound", FORCE_STRING)
AccessorFunc(PANEL, "tickSoundRange", "TickSoundRange")
AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha", FORCE_NUMBER)
AccessorFunc(PANEL, "expireTime", "ExpireTime", FORCE_NUMBER)
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
function PANEL:Init()
self:DockPadding(4, 4, 4, 4)
self:SetSize(self:GetParent():GetWide(), 0)
self.label = self:Add("DLabel")
self.label:Dock(FILL)
self.label:SetFont("ixMediumLightFont")
self.label:SetTextColor(color_white)
self.label:SetExpensiveShadow(1, color_black)
self.label:SetText("Area")
self.text = ""
self.tickSound = "ui/buttonrollover.wav"
self.tickSoundRange = {190, 200}
self.backgroundAlpha = 255
self.expireTime = 8
self.animationTime = 2
self.character = 1
self.createTime = RealTime()
self.currentAlpha = 255
self.currentHeight = 0
self.nextThink = RealTime()
end
function PANEL:Show()
self:CreateAnimation(0.5, {
index = -1,
target = {currentHeight = self.label:GetTall() + 8},
easing = "outQuint",
Think = function(animation, panel)
panel:SetTall(panel.currentHeight)
end
})
end
function PANEL:SetFont(font)
self.label:SetFont(font)
end
function PANEL:SetText(text)
if (text:sub(1, 1) == "@") then
text = L(text:sub(2))
end
self.label:SetText(text)
self.text = text
self.character = 1
end
function PANEL:Think()
local time = RealTime()
if (time >= self.nextThink) then
if (self.character < self.text:utf8len()) then
self.character = self.character + 1
self.label:SetText(string.utf8sub(self.text, 1, self.character))
LocalPlayer():EmitSound(self.tickSound, 100, math.random(self.tickSoundRange[1], self.tickSoundRange[2]))
end
if (time >= self.createTime + self.expireTime and !self.bRemoving) then
self:Remove()
end
self.nextThink = time + 0.05
end
end
function PANEL:SizeToContents()
self:SetWide(self:GetParent():GetWide())
self.label:SetWide(self:GetWide())
self.label:SizeToContentsY()
end
function PANEL:Paint(width, height)
self.backgroundAlpha = math.max(self.backgroundAlpha - 200 * FrameTime(), 0)
derma.SkinFunc("PaintAreaEntry", self, width, height)
end
function PANEL:Remove()
if (self.bRemoving) then
return
end
self:CreateAnimation(self.animationTime, {
target = {currentAlpha = 0},
Think = function(animation, panel)
panel:SetAlpha(panel.currentAlpha)
end,
OnComplete = function(animation, panel)
panel:CreateAnimation(0.5, {
index = -1,
target = {currentHeight = 0},
easing = "outQuint",
Think = function(_, sizePanel)
sizePanel:SetTall(sizePanel.currentHeight)
end,
OnComplete = function(_, sizePanel)
sizePanel:OnRemove()
BaseClass.Remove(sizePanel)
end
})
end
})
self.bRemoving = true
end
function PANEL:OnRemove()
end
vgui.Register("ixAreaEntry", PANEL, "Panel")
-- main panel
PANEL = {}
function PANEL:Init()
local chatWidth, _ = chat.GetChatBoxSize()
local _, chatY = chat.GetChatBoxPos()
self:SetSize(chatWidth, chatY)
self:SetPos(32, 0)
self:ParentToHUD()
self.entries = {}
ix.gui.area = self
end
function PANEL:AddEntry(entry, color)
color = color or ix.config.Get("color")
local id = #self.entries + 1
local panel = entry
if (isstring(entry)) then
panel = self:Add("ixAreaEntry")
panel:SetText(entry)
end
panel:SetBackgroundColor(color)
panel:SizeToContents()
panel:Dock(BOTTOM)
panel:Show()
panel.OnRemove = function()
for k, v in pairs(self.entries) do
if (v == panel) then
table.remove(self.entries, k)
break
end
end
end
self.entries[id] = panel
return id
end
function PANEL:GetEntries()
return self.entries
end
vgui.Register("ixArea", PANEL, "Panel")
================================================
FILE: plugins/area/derma/cl_areaedit.lua
================================================
local PLUGIN = PLUGIN
local PANEL = {}
function PANEL:Init()
if (IsValid(ix.gui.areaEdit)) then
ix.gui.areaEdit:Remove()
end
ix.gui.areaEdit = self
self.list = {}
self.properties = {}
self:SetDeleteOnClose(true)
self:SetSizable(true)
self:SetTitle(L("areaNew"))
-- scroll panel
self.canvas = self:Add("DScrollPanel")
self.canvas:Dock(FILL)
-- name entry
self.nameEntry = vgui.Create("ixTextEntry")
self.nameEntry:SetFont("ixMediumLightFont")
self.nameEntry:SetText(L("areaNew"))
local listRow = self.canvas:Add("ixListRow")
listRow:SetList(self.list)
listRow:SetLabelText(L("name"))
listRow:SetRightPanel(self.nameEntry)
listRow:Dock(TOP)
listRow:SizeToContents()
-- type entry
self.typeEntry = self.canvas:Add("DComboBox")
self.typeEntry:Dock(RIGHT)
self.typeEntry:SetFont("ixMediumLightFont")
self.typeEntry:SetTextColor(color_black)
self.typeEntry.OnSelect = function(panel)
panel:SizeToContents()
panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice)
end
for id, name in pairs(ix.area.types) do
self.typeEntry:AddChoice(L(name), id, id == "area")
end
listRow = self.canvas:Add("ixListRow")
listRow:SetList(self.list)
listRow:SetLabelText(L("type"))
listRow:SetRightPanel(self.typeEntry)
listRow:Dock(TOP)
listRow:SizeToContents()
-- properties
for k, v in pairs(ix.area.properties) do
local panel
if (v.type == ix.type.string or v.type == ix.type.number) then
panel = vgui.Create("ixTextEntry")
panel:SetFont("ixMenuButtonFont")
panel:SetText(tostring(v.default))
if (v.type == ix.type.number) then
panel.realGetValue = panel.GetValue
panel.GetValue = function()
return tonumber(panel:realGetValue()) or v.default
end
end
elseif (v.type == ix.type.bool) then
panel = vgui.Create("ixCheckBox")
panel:SetChecked(v.default, true)
panel:SetFont("ixMediumLightFont")
elseif (v.type == ix.type.color) then
panel = vgui.Create("DButton")
panel.value = v.default
panel:SetText("")
panel:SetSize(64, 64)
panel.picker = vgui.Create("DColorCombo")
panel.picker:SetColor(panel.value)
panel.picker:SetVisible(false)
panel.picker.OnValueChanged = function(_, newColor)
panel.value = newColor
end
panel.Paint = function(_, width, height)
surface.SetDrawColor(0, 0, 0, 255)
surface.DrawOutlinedRect(0, 0, width, height)
surface.SetDrawColor(panel.value)
surface.DrawRect(4, 4, width - 8, height - 8)
end
panel.DoClick = function()
if (!panel.picker:IsVisible()) then
local x, y = panel:LocalToScreen(0, 0)
panel.picker:SetPos(x, y + 32)
panel.picker:SetColor(panel.value)
panel.picker:SetVisible(true)
panel.picker:MakePopup()
else
panel.picker:SetVisible(false)
end
end
panel.OnRemove = function()
panel.picker:Remove()
end
panel.GetValue = function()
return panel.picker:GetColor()
end
end
if (IsValid(panel)) then
local row = self.canvas:Add("ixListRow")
row:SetList(self.list)
row:SetLabelText(L(k))
row:SetRightPanel(panel)
row:Dock(TOP)
row:SizeToContents()
end
self.properties[k] = function()
return panel:GetValue()
end
end
-- save button
self.saveButton = self:Add("DButton")
self.saveButton:SetText(L("save"))
self.saveButton:SizeToContents()
self.saveButton:Dock(BOTTOM)
self.saveButton.DoClick = function()
self:Submit()
end
self:SizeToContents()
self:SetPos(64, 0)
self:CenterVertical()
end
function PANEL:SizeToContents()
local width = 600
local height = 37
for _, v in ipairs(self.canvas:GetCanvas():GetChildren()) do
width = math.max(width, v:GetLabelWidth())
height = height + v:GetTall()
end
self:SetWide(width + 200)
self:SetTall(height + self.saveButton:GetTall() + 50)
end
function PANEL:Submit()
local name = self.nameEntry:GetValue()
if (ix.area.stored[name]) then
ix.util.NotifyLocalized("areaAlreadyExists")
return
end
local properties = {}
for k, v in pairs(self.properties) do
properties[k] = v()
end
local _, type = self.typeEntry:GetSelected()
net.Start("ixAreaAdd")
net.WriteString(name)
net.WriteString(type)
net.WriteVector(PLUGIN.editStart)
net.WriteVector(PLUGIN:GetPlayerAreaTrace().HitPos)
net.WriteTable(properties)
net.SendToServer()
PLUGIN.editStart = nil
self:Remove()
end
function PANEL:OnRemove()
PLUGIN.editProperties = nil
end
vgui.Register("ixAreaEdit", PANEL, "DFrame")
if (IsValid(ix.gui.areaEdit)) then
ix.gui.areaEdit:Remove()
end
================================================
FILE: plugins/area/languages/sh_english.lua
================================================
LANGUAGE = {
area = "Area",
areas = "Areas",
areaEditMode = "Area Edit Mode",
areaNew = "New Area",
areaAlreadyExists = "An area with this name already exists!",
areaDoesntExist = "An area with that name doesn't exist!",
areaInvalidType = "You have specified an invalid area type!",
areaEditTip = "Click to start creating an area. Right click to exit.",
areaFinishTip = "Click again to finish drawing the area. Right click to go back.",
areaRemoveTip = "Press reload to remove the area you're currently in.",
areaDeleteConfirm = "Are you sure you want to delete the area \"%s\"?",
areaDelete = "Delete Area",
cmdAreaEdit = "Enters area edit mode."
}
================================================
FILE: plugins/area/languages/sh_russian.lua
================================================
LANGUAGE = {
area = "Зона",
areas = "Зоны",
areaEditMode = "Редактирование зоны",
areaNew = "Новая зона",
areaAlreadyExists = "Зона с таким названием уже существует!",
areaDoesntExist = "Зона с таким названием не существует!",
areaInvalidType = "Вы указали неверный тип зоны!",
areaEditTip = "Нажмите, чтобы начать создание зоны. Щелкните правой кнопкой мыши, чтобы выйти.",
areaFinishTip = "Нажмите еще раз, чтобы закончить создание зоны. Щелкните правой кнопкой мыши, чтобы вернуться.",
areaRemoveTip = "Нажмите перезарядку, чтобы удалить зону, в которой вы находитесь.",
areaDeleteConfirm = "Вы уверены, что хотите удалить зону \"%s\"?",
areaDelete = "Удалить зону",
cmdAreaEdit = "Вход в режим редактирования зоны."
}
================================================
FILE: plugins/area/sh_plugin.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Areas"
PLUGIN.author = "`impulse"
PLUGIN.description = "Provides customizable area definitions."
ix.area = ix.area or {}
ix.area.types = ix.area.types or {}
ix.area.properties = ix.area.properties or {}
ix.area.stored = ix.area.stored or {}
ix.config.Add("areaTickTime", 1, "How many seconds between each time a character's current area is calculated.",
function(oldValue, newValue)
if (SERVER) then
timer.Adjust("ixAreaThink", newValue)
end
end,
{
data = {min = 0.1, max = 4},
category = "areas"
}
)
function ix.area.AddProperty(name, type, default, data)
ix.area.properties[name] = {
type = type,
default = default,
data = data or {}
}
end
function ix.area.AddType(type, name)
name = name or type
-- only store localized strings on the client
ix.area.types[type] = CLIENT and name or true
end
function PLUGIN:SetupAreaProperties()
ix.area.AddType("area")
ix.area.AddProperty("color", ix.type.color, ix.config.Get("color"))
ix.area.AddProperty("display", ix.type.bool, true)
end
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")
ix.util.Include("sv_hooks.lua")
ix.util.Include("cl_hooks.lua")
-- return world center, local min, and local max from world start/end positions
function PLUGIN:GetLocalAreaPosition(startPosition, endPosition)
local center = LerpVector(0.5, startPosition, endPosition)
local min = WorldToLocal(startPosition, angle_zero, center, angle_zero)
local max = WorldToLocal(endPosition, angle_zero, center, angle_zero)
return center, min, max
end
do
local COMMAND = {}
COMMAND.description = "@cmdAreaEdit"
COMMAND.adminOnly = true
function COMMAND:OnRun(client)
client:SetWepRaised(false)
net.Start("ixAreaEditStart")
net.Send(client)
end
ix.command.Add("AreaEdit", COMMAND)
end
do
local PLAYER = FindMetaTable("Player")
-- returns the current area the player is in, or the last valid one if the player is not in an area
function PLAYER:GetArea()
return self.ixArea
end
-- returns true if the player is in any area, this does not use the last valid area like GetArea does
function PLAYER:IsInArea()
return self.ixInArea
end
end
================================================
FILE: plugins/area/sv_hooks.lua
================================================
function PLUGIN:LoadData()
hook.Run("SetupAreaProperties")
ix.area.stored = self:GetData() or {}
timer.Create("ixAreaThink", ix.config.Get("areaTickTime"), 0, function()
self:AreaThink()
end)
end
function PLUGIN:SaveData()
self:SetData(ix.area.stored)
end
function PLUGIN:PlayerInitialSpawn(client)
timer.Simple(1, function()
if (IsValid(client)) then
local json = util.TableToJSON(ix.area.stored)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixAreaSync")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
function PLUGIN:PlayerLoadedCharacter(client)
client.ixArea = ""
client.ixInArea = nil
end
function PLUGIN:PlayerSpawn(client)
client.ixArea = ""
client.ixInArea = nil
end
function PLUGIN:AreaThink()
for _, client in player.Iterator() do
local character = client:GetCharacter()
if (!client:Alive() or !character) then
continue
end
local overlappingBoxes = {}
local position = client:GetPos() + client:OBBCenter()
for id, info in pairs(ix.area.stored) do
if (position:WithinAABox(info.startPosition, info.endPosition)) then
overlappingBoxes[#overlappingBoxes + 1] = id
end
end
if (#overlappingBoxes > 0) then
local oldID = client:GetArea()
local id = overlappingBoxes[1]
if (oldID != id) then
hook.Run("OnPlayerAreaChanged", client, client.ixArea, id)
client.ixArea = id
end
client.ixInArea = true
else
client.ixInArea = false
end
end
end
function PLUGIN:OnPlayerAreaChanged(client, oldID, newID)
net.Start("ixAreaChanged")
net.WriteString(oldID)
net.WriteString(newID)
net.Send(client)
end
net.Receive("ixAreaAdd", function(length, client)
if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then
return
end
local id = net.ReadString()
local type = net.ReadString()
local startPosition, endPosition = net.ReadVector(), net.ReadVector()
local properties = net.ReadTable()
if (!ix.area.types[type]) then
client:NotifyLocalized("areaInvalidType")
return
end
if (ix.area.stored[id]) then
client:NotifyLocalized("areaAlreadyExists")
return
end
for k, v in pairs(properties) do
if (!isstring(k) or !ix.area.properties[k]) then
continue
end
properties[k] = ix.util.SanitizeType(ix.area.properties[k].type, v)
end
ix.area.Create(id, type, startPosition, endPosition, nil, properties)
ix.log.Add(client, "areaAdd", id)
end)
net.Receive("ixAreaRemove", function(length, client)
if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then
return
end
local id = net.ReadString()
if (!ix.area.stored[id]) then
client:NotifyLocalized("areaDoesntExist")
return
end
ix.area.Remove(id)
ix.log.Add(client, "areaRemove", id)
end)
================================================
FILE: plugins/area/sv_plugin.lua
================================================
util.AddNetworkString("ixAreaSync")
util.AddNetworkString("ixAreaAdd")
util.AddNetworkString("ixAreaRemove")
util.AddNetworkString("ixAreaChanged")
util.AddNetworkString("ixAreaEditStart")
util.AddNetworkString("ixAreaEditEnd")
ix.log.AddType("areaAdd", function(client, name)
return string.format("%s has added area \"%s\".", client:Name(), tostring(name))
end)
ix.log.AddType("areaRemove", function(client, name)
return string.format("%s has removed area \"%s\".", client:Name(), tostring(name))
end)
local function SortVector(first, second)
return Vector(math.min(first.x, second.x), math.min(first.y, second.y), math.min(first.z, second.z)),
Vector(math.max(first.x, second.x), math.max(first.y, second.y), math.max(first.z, second.z))
end
function ix.area.Create(name, type, startPosition, endPosition, bNoReplicate, properties)
local min, max = SortVector(startPosition, endPosition)
ix.area.stored[name] = {
type = type or "area",
startPosition = min,
endPosition = max,
bNoReplicate = bNoReplicate,
properties = properties
}
-- network to clients if needed
if (!bNoReplicate) then
net.Start("ixAreaAdd")
net.WriteString(name)
net.WriteString(type)
net.WriteVector(startPosition)
net.WriteVector(endPosition)
net.WriteTable(properties)
net.Broadcast()
end
end
function ix.area.Remove(name, bNoReplicate)
ix.area.stored[name] = nil
-- network to clients if needed
if (!bNoReplicate) then
net.Start("ixAreaRemove")
net.WriteString(name)
net.Broadcast()
end
end
================================================
FILE: plugins/chatbox/derma/cl_chatbox.lua
================================================
local PLUGIN = PLUGIN
local animationTime = 0.5
local chatBorder = 32
local sizingBorder = 20
local maxChatEntries = 100
-- called when a markup object should paint its text
local function PaintMarkupOverride(text, font, x, y, color, alignX, alignY, alpha)
alpha = alpha or 255
if (ix.option.Get("chatOutline", false)) then
-- outlined background for even more visibility
draw.SimpleTextOutlined(text, font, x, y, ColorAlpha(color, alpha), alignX, alignY, 1, Color(0, 0, 0, alpha))
else
-- background for easier reading
surface.SetTextPos(x + 1, y + 1)
surface.SetTextColor(0, 0, 0, alpha)
surface.SetFont(font)
surface.DrawText(text)
surface.SetTextPos(x, y)
surface.SetTextColor(color.r, color.g, color.b, alpha)
surface.SetFont(font)
surface.DrawText(text)
end
end
-- chat message
local PANEL = {}
AccessorFunc(PANEL, "fadeDelay", "FadeDelay", FORCE_NUMBER)
AccessorFunc(PANEL, "fadeDuration", "FadeDuration", FORCE_NUMBER)
function PANEL:Init()
self.text = ""
self.alpha = 255
self.fadeDelay = 15
self.fadeDuration = 5
end
function PANEL:SetMarkup(text)
self.text = text
self.markup = ix.markup.Parse(self.text, self:GetWide())
self.markup.onDrawText = PaintMarkupOverride
self:SetTall(self.markup:GetHeight())
timer.Simple(self.fadeDelay, function()
if (!IsValid(self)) then
return
end
self:CreateAnimation(self.fadeDuration, {
index = 3,
target = {alpha = 0}
})
end)
end
function PANEL:PerformLayout(width, height)
if ((IsValid(ix.gui.chat) and ix.gui.chat.bSizing) or width == self.markup:GetWidth()) then
return
end
self.markup = ix.markup.Parse(self.text, width)
self.markup.onDrawText = PaintMarkupOverride
self:SetTall(self.markup:GetHeight())
end
function PANEL:Paint(width, height)
local newAlpha
-- we'll want to hide the chat while some important menus are open
if (IsValid(ix.gui.characterMenu)) then
newAlpha = math.min(255 - ix.gui.characterMenu.currentAlpha, self.alpha)
elseif (IsValid(ix.gui.menu)) then
newAlpha = math.min(255 - ix.gui.menu.currentAlpha, self.alpha)
elseif (ix.gui.chat:GetActive()) then
newAlpha = math.max(ix.gui.chat.alpha, self.alpha)
else
newAlpha = self.alpha
end
if (newAlpha < 1) then
return
end
self.markup:draw(0, 0, nil, nil, newAlpha)
end
vgui.Register("ixChatMessage", PANEL, "Panel")
-- chatbox tab button
PANEL = {}
AccessorFunc(PANEL, "bActive", "Active", FORCE_BOOL)
AccessorFunc(PANEL, "bUnread", "Unread", FORCE_BOOL)
function PANEL:Init()
self:SetFont("ixChatFont")
self:SetContentAlignment(5)
self.unreadAlpha = 0
end
function PANEL:SetUnread(bValue)
self.bUnread = bValue
self:CreateAnimation(animationTime, {
index = 4,
target = {unreadAlpha = bValue and 1 or 0},
easing = "outQuint"
})
end
function PANEL:SizeToContents()
local width, height = self:GetContentSize()
self:SetSize(width + 12, height + 6)
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintChatboxTabButton", self, width, height)
end
vgui.Register("ixChatboxTabButton", PANEL, "DButton")
-- chatbox tab panel
-- holds all tab buttons and corresponding history panels
PANEL = {}
function PANEL:Init()
-- holds all tab buttons
self.buttons = self:Add("Panel")
self.buttons:Dock(TOP)
self.buttons:DockPadding(1, 1, 0, 0)
self.buttons.OnMousePressed = ix.util.Bind(ix.gui.chat, ix.gui.chat.OnMousePressed) -- we want mouse events to fall through
self.buttons.OnMouseReleased = ix.util.Bind(ix.gui.chat, ix.gui.chat.OnMouseReleased)
self.buttons.Paint = function(_, width, height)
derma.SkinFunc("PaintChatboxTabs", self, width, height)
end
self.tabs = {}
end
function PANEL:GetTabs()
return self.tabs
end
function PANEL:AddTab(id, filter)
local button = self.buttons:Add("ixChatboxTabButton")
button:Dock(LEFT)
button:SetText(id) -- display name is also the ID
button:SetActive(false)
button:SetMouseInputEnabled(true)
button:SizeToContents()
button.DoClick = function(this)
self:SetActiveTab(this:GetText())
end
local panel = self:Add("ixChatboxHistory")
panel:SetButton(button)
panel:SetID(id)
panel:Dock(FILL)
panel:SetVisible(false)
panel:SetFilter(filter or {})
button.DoRightClick = function(this)
ix.gui.chat:OnTabRightClick(this, panel, panel:GetID())
end
self.tabs[id] = panel
return panel
end
function PANEL:RemoveTab(id)
local tab = self.tabs[id]
if (!tab) then
return
end
tab:GetButton():Remove()
tab:Remove()
self.tabs[id] = nil
-- add default tab if we don't have any tabs left
if (table.IsEmpty(self.tabs)) then
self:AddTab(L("chat"), {})
self:SetActiveTab(L("chat"))
elseif (id == self:GetActiveTabID()) then
-- set a different active tab if we've removed a tab that is currently active
self:SetActiveTab(next(self.tabs))
end
end
function PANEL:RenameTab(id, newID)
local tab = self.tabs[id]
if (!tab) then
return
end
tab:GetButton():SetText(newID)
tab:GetButton():SizeToContents()
tab:SetID(newID)
self.tabs[id] = nil
self.tabs[newID] = tab
if (id == self:GetActiveTabID()) then
self:SetActiveTab(newID)
end
end
function PANEL:SetActiveTab(id)
local tab = self.tabs[id]
if (!tab) then
error("attempted to set non-existent active tab")
end
for _, v in ipairs(self.buttons:GetChildren()) do
v:SetActive(v:GetText() == id)
end
for _, v in pairs(self.tabs) do
v:SetVisible(v:GetID() == id)
end
tab:GetButton():SetUnread(false)
self.activeTab = id
self:OnTabChanged(tab)
end
function PANEL:GetActiveTabID()
return self.activeTab
end
function PANEL:GetActiveTab()
return self.tabs[self.activeTab]
end
-- called when the active tab is changed
-- `panel` is the corresponding history panel
function PANEL:OnTabChanged(panel)
end
vgui.Register("ixChatboxTabs", PANEL, "EditablePanel")
-- chatbox history panel
-- holds individual messages in a scrollable panel
PANEL = {}
AccessorFunc(PANEL, "filter", "Filter") -- blacklist of message classes
AccessorFunc(PANEL, "id", "ID", FORCE_STRING)
AccessorFunc(PANEL, "button", "Button") -- button panel that this panel corresponds to
function PANEL:Init()
self:DockMargin(4, 2, 4, 4) -- smaller top margin to help blend tab button/history panel transition
self:SetPaintedManually(true)
local bar = self:GetVBar()
bar:SetWide(0)
self.entries = {}
self.filter = {}
end
DEFINE_BASECLASS("Panel") -- DScrollPanel doesn't have SetVisible member
function PANEL:SetVisible(bState)
self:GetCanvas():SetVisible(bState)
BaseClass.SetVisible(self, bState)
end
DEFINE_BASECLASS("DScrollPanel")
function PANEL:PerformLayoutInternal()
local bar = self:GetVBar()
local bScroll = !ix.gui.chat:GetActive() or bar.Scroll == bar.CanvasSize -- only scroll when we're not at the bottom/inactive
BaseClass.PerformLayoutInternal(self)
if (bScroll) then
self:ScrollToBottom()
end
end
function PANEL:ScrollToBottom()
local bar = self:GetVBar()
bar:SetScroll(bar.CanvasSize)
end
-- adds a line of text as described by its elements
function PANEL:AddLine(elements, bShouldScroll)
-- table.concat is faster than regular string concatenation where there are lots of strings to concatenate
local buffer = {
""
}
if (ix.option.Get("chatTimestamps", false)) then
buffer[#buffer + 1] = "("
if (ix.option.Get("24hourTime", false)) then
buffer[#buffer + 1] = os.date("%H:%M")
else
buffer[#buffer + 1] = os.date("%I:%M %p")
end
buffer[#buffer + 1] = ") "
end
if (CHAT_CLASS) then
buffer[#buffer + 1] = ""
end
for _, v in ipairs(elements) do
if (type(v) == "IMaterial") then
local texture = v:GetName()
if (texture) then
buffer[#buffer + 1] = string.format(" ", texture, v:Width(), v:Height())
end
elseif (istable(v) and v.r and v.g and v.b) then
buffer[#buffer + 1] = string.format("", v.r, v.g, v.b)
elseif (type(v) == "Player") then
local color = team.GetColor(v:Team())
buffer[#buffer + 1] = string.format("%s", color.r, color.g, color.b,
v:GetName():gsub("<", "<"):gsub(">", ">"))
else
buffer[#buffer + 1] = tostring(v):gsub("<", "<"):gsub(">", ">"):gsub("%b**", function(value)
local inner = value:utf8sub(2, -2)
if (inner:find("%S")) then
return "" .. value:utf8sub(2, -2) .. ""
end
end)
end
end
local panel = self:Add("ixChatMessage")
panel:Dock(TOP)
panel:InvalidateParent(true)
panel:SetMarkup(table.concat(buffer))
if (#self.entries >= maxChatEntries) then
local oldPanel = table.remove(self.entries, 1)
if (IsValid(oldPanel)) then
oldPanel:Remove()
end
end
self.entries[#self.entries + 1] = panel
return panel
end
vgui.Register("ixChatboxHistory", PANEL, "DScrollPanel")
PANEL = {}
DEFINE_BASECLASS("DTextEntry")
function PANEL:Init()
self:SetFont("ixChatFont")
self:SetUpdateOnType(true)
self:SetHistoryEnabled(true)
self.History = ix.chat.history
self.m_bLoseFocusOnClickAway = false
end
function PANEL:SetFont(font)
BaseClass.SetFont(self, font)
surface.SetFont(font)
local _, height = surface.GetTextSize("W@")
self:SetTall(height + 8)
end
function PANEL:AllowInput(newCharacter)
local text = self:GetText()
local maxLength = ix.config.Get("chatMax")
-- we can't check for the proper length using utf-8 since AllowInput is called for single bytes instead of full characters
if (string.len(text .. newCharacter) > maxLength) then
surface.PlaySound("common/talk.wav")
return true
end
end
function PANEL:Think()
local text = self:GetText()
local maxLength = ix.config.Get("chatMax", 256)
if (text:utf8len() > maxLength) then
local newText = text:utf8sub(0, maxLength)
self:SetText(newText)
self:SetCaretPos(newText:utf8len())
end
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintChatboxEntry", self, width, height)
end
vgui.Register("ixChatboxEntry", PANEL, "DTextEntry")
-- chatbox additional command info panel
PANEL = {}
AccessorFunc(PANEL, "text", "Text", FORCE_STRING)
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
AccessorFunc(PANEL, "textColor", "TextColor")
function PANEL:Init()
self.text = ""
self.padding = 4
self.currentWidth = 0
self.currentMargin = 0
self.backgroundColor = ix.config.Get("color")
self.textColor = color_white
self:SetWide(0)
self:DockMargin(0, 0, 0, 0)
end
function PANEL:SetText(text)
self:SetVisible(true)
if (!isstring(text) or text == "") then
self:CreateAnimation(animationTime, {
index = 9,
easing = "outQuint",
target = {
currentWidth = 0,
currentMargin = 0
},
Think = function(animation, panel)
panel:SetWide(panel.currentWidth)
panel:DockMargin(0, 0, panel.currentMargin, 0)
end,
OnComplete = function(animation, panel)
panel:SetVisible(false)
self.text = ""
end
})
else
text = tostring(text)
surface.SetFont("ixChatFont")
local textWidth = surface.GetTextSize(text)
self:CreateAnimation(animationTime, {
index = 9,
easing = "outQuint",
target = {
currentWidth = textWidth + self.padding * 2,
currentMargin = 4
},
Think = function(animation, panel)
panel:SetWide(panel.currentWidth)
panel:DockMargin(0, 0, panel.currentMargin, 0)
end,
})
self.text = text
end
end
function PANEL:Paint(width, height)
derma.SkinFunc("DrawChatboxPrefixBox", self, width, height)
surface.SetFont("ixChatFont")
local textWidth, textHeight = surface.GetTextSize(self.text)
surface.SetTextColor(self.textColor)
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5)
surface.DrawText(self.text)
end
vgui.Register("ixChatboxPrefix", PANEL, "Panel")
-- chatbox command preview panel
PANEL = {}
DEFINE_BASECLASS("Panel")
AccessorFunc(PANEL, "targetHeight", "TargetHeight", FORCE_NUMBER)
AccessorFunc(PANEL, "command", "Command", FORCE_STRING)
function PANEL:Init()
self:SetTall(0)
self:SetVisible(false, true)
self.height = 0
self.targetHeight = 16
self.margin = 0
self.command = ""
end
function PANEL:SetCommand(command)
-- if we're setting it to an empty command, then we'll hold the reference to the old command table to render it for the
-- fade out animation
if (command == "") then
self.command = ""
ix.chat.currentCommand = ""
return
end
local commandTable = ix.command.list[command]
if (!commandTable) then
return
end
self.command = command
self.commandTable = commandTable
self.arguments = {}
ix.chat.currentCommand = command:lower()
end
function PANEL:UpdateArguments(text)
if (self.command == "") then
ix.chat.currentArguments = {}
return
end
local commandName = text:match("(/(%w+)%s)") or self.command -- we could be using a chat class prefix and not a proper command
local givenArguments = ix.command.ExtractArgs(text:utf8sub(commandName:utf8len()))
local commandArguments = self.commandTable.arguments or {}
local arguments = {}
-- we want to concat any text types so they show up as one argument at the end of the list, this is so the argument
-- highlighting is accurate since ExtractArgs will not account because it has no type context
for k, v in ipairs(givenArguments) do
if (k == #commandArguments) then
arguments[#arguments + 1] = table.concat(givenArguments, " ", k)
break
end
arguments[#arguments + 1] = v
end
self.arguments = arguments
ix.chat.currentArguments = table.Copy(arguments)
end
-- returns the target SetVisible value
function PANEL:IsOpen()
return self.bOpen
end
function PANEL:SetVisible(bValue, bForce)
if (bForce) then
BaseClass.SetVisible(self, bValue)
return
end
BaseClass.SetVisible(self, true) -- make sure this panel is visible during animation
self.bOpen = bValue
self:CreateAnimation(animationTime * 0.5, {
index = 5,
target = {
height = bValue and self.targetHeight or 0,
margin = bValue and 4 or 0
},
easing = "outQuint",
Think = function(animation, panel)
panel:SetTall(math.ceil(panel.height))
panel:DockMargin(4, 0, 4, math.ceil(panel.margin))
end,
OnComplete = function(animation, panel)
BaseClass.SetVisible(panel, bValue)
end
})
end
function PANEL:Paint(width, height)
local command = self.commandTable
if (!command) then
return
end
local color = ix.config.Get("color")
surface.SetFont("ixChatFont")
-- command name
local x = derma.SkinFunc("DrawChatboxPreviewBox", 0, 0, "/" .. command.name) + 6
-- command arguments
if (istable(command.arguments)) then
for k, v in ipairs(command.arguments) do
local bOptional = bit.band(v, ix.type.optional) > 0
local type = bOptional and bit.bxor(v, ix.type.optional) or v
x = x + derma.SkinFunc(
"DrawChatboxPreviewBox", x, 0,
-- draw text in format of or [name: type] if it's optional
string.format(bOptional and "[%s: %s]" or "<%s: %s>", command.argumentNames[k], ix.type[type]),
-- fill in the color for arguments that are before the one the user is currently typing, otherwise draw a faded
-- color instead (optional arguments will not have any background color unless it's been filled out by user)
(k <= #self.arguments) and color or (bOptional and Color(0, 0, 0, 66) or ColorAlpha(color, 100))
) + 6
end
end
end
vgui.Register("ixChatboxPreview", PANEL, "Panel")
-- chatbox autocomplete panel
-- holds and displays similar commands based on the textentry
PANEL = {}
DEFINE_BASECLASS("Panel")
AccessorFunc(PANEL, "maxEntries", "MaxEntries", FORCE_NUMBER)
function PANEL:Init()
self:SetVisible(false, true)
self:SetMouseInputEnabled(true)
self.maxEntries = 20
self.currentAlpha = 0
self.commandIndex = 0 -- currently selected entry in command list
self.commands = {}
self.commandPanels = {}
end
function PANEL:GetCommands()
return self.commands
end
function PANEL:IsOpen()
return self.bOpen
end
function PANEL:SetVisible(bValue, bForce)
if (bForce) then
BaseClass.SetVisible(self, bValue)
return
end
BaseClass.SetVisible(self, true) -- make sure this panel is visible during animation
self.bOpen = bValue
self:CreateAnimation(animationTime, {
index = 6,
target = {
currentAlpha = bValue and 255 or 0
},
easing = "outQuint",
Think = function(animation, panel)
panel:SetAlpha(math.ceil(panel.currentAlpha))
end,
OnComplete = function(animation, panel)
BaseClass.SetVisible(panel, bValue)
if (!bValue) then
self.commands = {}
end
end
})
end
function PANEL:Update(text)
local commands = ix.command.FindAll(text, true, true, true)
self.commandIndex = 0 -- reset the command index because the command list could be different
self.commands = {}
for _, v in ipairs(self.commandPanels) do
v:Remove()
end
self.commandPanels = {}
-- manually loop over the found commands so we can ignore commands the user doesn't have access to
local i = 1
local bSelected -- just to make sure we don't reset it during the loop for whatever reason
for _, v in ipairs(commands) do
-- @todo chat classes aren't checked since they're done through the class's OnCanSay callback
if (v.OnCheckAccess and !v:OnCheckAccess(LocalPlayer())) then
continue
end
local panel = self:Add("ixChatboxAutocompleteEntry")
panel:SetCommand(v)
if (!bSelected and text:utf8lower():utf8sub(1, v.uniqueID:utf8len()) == v.uniqueID) then
panel:SetHighlighted(true)
self.commandIndex = i
bSelected = true
end
self.commandPanels[i] = panel
self.commands[i] = v
if (i == self.maxEntries) then
break
end
i = i + 1
end
end
-- selects the next entry in the autocomplete if possible and returns the text that should replace the textentry
function PANEL:SelectNext()
-- wrap back to beginning if we're past the end
if (self.commandIndex == #self.commands) then
self.commandIndex = 1
else
self.commandIndex = self.commandIndex + 1
end
for k, v in ipairs(self.commandPanels) do
if (k == self.commandIndex) then
v:SetHighlighted(true)
self:ScrollToChild(v)
else
v:SetHighlighted(false)
end
end
return "/" .. self.commands[self.commandIndex].uniqueID
end
function PANEL:Paint(width, height)
ix.util.DrawBlur(self)
surface.SetDrawColor(0, 0, 0, 200)
surface.DrawRect(0, 0, width, height)
end
vgui.Register("ixChatboxAutocomplete", PANEL, "DScrollPanel")
-- autocomplete entry
PANEL = {}
AccessorFunc(PANEL, "bSelected", "Highlighted", FORCE_BOOL)
function PANEL:Init()
self:Dock(TOP)
self.name = self:Add("DLabel")
self.name:Dock(TOP)
self.name:DockMargin(4, 4, 0, 0)
self.name:SetContentAlignment(4)
self.name:SetFont("ixChatFont")
self.name:SetTextColor(ix.config.Get("color"))
self.name:SetExpensiveShadow(1, color_black)
self.description = self:Add("DLabel")
self.description:Dock(BOTTOM)
self.description:DockMargin(4, 4, 0, 4)
self.description:SetContentAlignment(4)
self.description:SetFont("ixChatFont")
self.description:SetTextColor(color_white)
self.description:SetExpensiveShadow(1, color_black)
self.highlightAlpha = 0
end
function PANEL:SetHighlighted(bValue)
self:CreateAnimation(animationTime * 2, {
index = 7,
target = {highlightAlpha = bValue and 1 or 0},
easing = "outQuint"
})
self.bHighlighted = true
end
function PANEL:SetCommand(command)
local description = command:GetDescription()
self.name:SetText("/" .. command.name)
if (description and description != "") then
self.description:SetText(command:GetDescription())
else
self.description:SetVisible(false)
end
self:SizeToContents()
self.command = command
end
function PANEL:SizeToContents()
local bDescriptionVisible = self.description:IsVisible()
local _, height = self.name:GetContentSize()
self.name:SetTall(height)
if (bDescriptionVisible) then
_, height = self.description:GetContentSize()
self.description:SetTall(height)
else
self.description:SetTall(0)
end
self:SetTall(self.name:GetTall() + self.description:GetTall() + (bDescriptionVisible and 12 or 8))
end
function PANEL:Paint(width, height)
derma.SkinFunc("PaintChatboxAutocompleteEntry", self, width, height)
end
vgui.Register("ixChatboxAutocompleteEntry", PANEL, "Panel")
-- main chatbox panel
-- this contains the text entry, tab sheets, and callbacks for other panel events
PANEL = {}
AccessorFunc(PANEL, "bActive", "Active", FORCE_BOOL)
function PANEL:Init()
ix.gui.chat = self
self:SetSize(self:GetDefaultSize())
self:SetPos(self:GetDefaultPosition())
local entryPanel = self:Add("Panel")
entryPanel:SetZPos(1)
entryPanel:Dock(BOTTOM)
entryPanel:DockMargin(4, 0, 4, 4)
self.entry = entryPanel:Add("ixChatboxEntry")
self.entry:Dock(FILL)
self.entry.OnValueChange = ix.util.Bind(self, self.OnTextChanged)
self.entry.OnKeyCodeTyped = ix.util.Bind(self, self.OnKeyCodeTyped)
self.entry.OnEnter = ix.util.Bind(self, self.OnMessageSent)
self.prefix = entryPanel:Add("ixChatboxPrefix")
self.prefix:Dock(LEFT)
self.preview = self:Add("ixChatboxPreview")
self.preview:SetZPos(2) -- ensure the preview is docked above the text entry
self.preview:Dock(BOTTOM)
self.preview:SetTargetHeight(self.entry:GetTall())
self.tabs = self:Add("ixChatboxTabs")
self.tabs:Dock(FILL)
self.tabs.OnTabChanged = ix.util.Bind(self, self.OnTabChanged)
self.autocomplete = self.tabs:Add("ixChatboxAutocomplete")
self.autocomplete:Dock(FILL)
self.autocomplete:DockMargin(4, 3, 4, 4) -- top margin is 3 to account for tab 1px border
self.autocomplete:SetZPos(3)
self.alpha = 0
self:SetActive(false)
-- luacheck: globals chat
chat.GetChatBoxPos = function()
return self:GetPos()
end
chat.GetChatBoxSize = function()
return self:GetSize()
end
end
function PANEL:GetDefaultSize()
return ScrW() * 0.4, ScrH() * 0.375
end
function PANEL:GetDefaultPosition()
return chatBorder, ScrH() - self:GetTall() - chatBorder
end
DEFINE_BASECLASS("Panel")
function PANEL:SetAlpha(amount, duration)
self:CreateAnimation(duration or animationTime, {
index = 1,
target = {alpha = amount},
easing = "outQuint",
Think = function(animation, panel)
BaseClass.SetAlpha(panel, panel.alpha)
end
})
end
function PANEL:SizingInBounds()
local screenX, screenY = self:LocalToScreen(0, 0)
local mouseX, mouseY = gui.MousePos()
return mouseX > screenX + self:GetWide() - sizingBorder and mouseY > screenY + self:GetTall() - sizingBorder
end
function PANEL:DraggingInBounds()
local _, screenY = self:LocalToScreen(0, 0)
local mouseY = gui.MouseY()
return mouseY > screenY and mouseY < screenY + self.tabs.buttons:GetTall()
end
function PANEL:SetActive(bActive)
if (bActive) then
self:SetAlpha(255)
self:MakePopup()
self.entry:RequestFocus()
input.SetCursorPos(self:LocalToScreen(-1, -1))
hook.Run("StartChat")
self.prefix:SetText(hook.Run("GetChatPrefixInfo", ""))
else
-- make sure we aren't still sizing/dragging anything
if (self.bSizing or self.DragOffset) then
self:OnMouseReleased(MOUSE_LEFT)
end
self:SetAlpha(0)
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
self.autocomplete:SetVisible(false)
self.preview:SetVisible(false)
self.entry:SetText("")
self.preview:SetCommand("")
self.prefix:SetText(hook.Run("GetChatPrefixInfo", ""))
CloseDermaMenus()
gui.EnableScreenClicker(false)
hook.Run("FinishChat")
end
local tab = self.tabs:GetActiveTab()
if (tab) then
-- we'll scroll to bottom even if we're opening since the SetVisible for the textentry will shift things a bit
tab:ScrollToBottom()
end
self.bActive = tobool(bActive)
end
function PANEL:SetupTabs(tabs)
if (!tabs or table.IsEmpty(tabs)) then
self.tabs:AddTab(L("chat"), {})
self.tabs:SetActiveTab(L("chat"))
return
end
for id, filter in pairs(tabs) do
self.tabs:AddTab(id, filter)
end
self.tabs:SetActiveTab(next(tabs))
end
function PANEL:SetupPosition(info)
local x, y, width, height
if (!istable(info)) then
x, y = self:GetDefaultPosition()
width, height = self:GetDefaultSize()
else
-- screen size may have changed so we'll need to clamp the values
width = math.Clamp(info[3], 32, ScrW() - chatBorder * 2)
height = math.Clamp(info[4], 32, ScrH() - chatBorder * 2)
x = math.Clamp(info[1], 0, ScrW() - width)
y = math.Clamp(info[2], 0, ScrH() - height)
end
self:SetSize(width, height)
self:SetPos(x, y)
PLUGIN:SavePosition()
end
function PANEL:OnMousePressed(key)
if (key == MOUSE_RIGHT) then
local menu = DermaMenu()
menu:AddOption(L("chatNewTab"), function()
if (IsValid(ix.gui.chatTabCustomize)) then
ix.gui.chatTabCustomize:Remove()
end
local panel = vgui.Create("ixChatboxTabCustomize")
panel.OnTabCreated = ix.util.Bind(self, self.OnTabCreated)
end)
menu:AddOption(L("chatMarkRead"), function()
for _, v in pairs(self.tabs:GetTabs()) do
v:GetButton():SetUnread(false)
end
end)
menu:AddSpacer()
menu:AddOption(L("chatReset"), function()
local x, y = self:GetDefaultPosition()
local width, height = self:GetDefaultSize()
self:SetSize(width, height)
self:SetPos(x, y)
ix.option.Set("chatPosition", "")
hook.Run("ChatboxPositionChanged", x, y, width, height)
end)
menu:AddOption(L("chatResetTabs"), function()
for id, _ in pairs(self.tabs:GetTabs()) do
self.tabs:RemoveTab(id)
end
ix.option.Set("chatTabs", "")
end)
menu:Open()
menu:MakePopup()
return
end
if (key != MOUSE_LEFT) then
return
end
-- capture the mouse if we're in bounds for sizing this panel
if (self:SizingInBounds()) then
self.bSizing = true
self:MouseCapture(true)
elseif (self:DraggingInBounds()) then
local mouseX, mouseY = self:ScreenToLocal(gui.MousePos())
-- mouse offset relative to the panel
self.DragOffset = {mouseX, mouseY}
self:MouseCapture(true)
end
end
function PANEL:OnMouseReleased()
self:MouseCapture(false)
self:SetCursor("arrow")
-- save new position/size if we were dragging/resizing
if (self.bSizing or self.DragOffset) then
PLUGIN:SavePosition()
self.bSizing = nil
self.DragOffset = nil
-- resize chat messages to fit new width
self:InvalidateChildren(true)
local x, y = self:GetPos()
local width, height = self:GetSize()
hook.Run("ChatboxPositionChanged", x, y, width, height)
end
end
function PANEL:Think()
if (!self.bActive) then
return
end
local mouseX = math.Clamp(gui.MouseX(), 0, ScrW())
local mouseY = math.Clamp(gui.MouseY(), 0, ScrH())
if (self.bSizing) then
local x, y = self:GetPos()
local width = math.Clamp(mouseX - x, chatBorder, ScrW() - chatBorder * 2)
local height = math.Clamp(mouseY - y, chatBorder, ScrH() - chatBorder * 2)
self:SetSize(width, height)
self:SetCursor("sizenwse")
elseif (self.DragOffset) then
local x = math.Clamp(mouseX - self.DragOffset[1], 0, ScrW() - self:GetWide())
local y = math.Clamp(mouseY - self.DragOffset[2], 0, ScrH() - self:GetTall())
self:SetPos(x, y)
elseif (self:SizingInBounds()) then
self:SetCursor("sizenwse")
elseif (self:DraggingInBounds()) then
-- we have to set the cursor on the list panel since that's the actual hovered panel
self.tabs.buttons:SetCursor("sizeall")
else
self:SetCursor("arrow")
end
end
function PANEL:Paint(width, height)
local tab = self.tabs:GetActiveTab()
local alpha = self:GetAlpha()
derma.SkinFunc("PaintChatboxBackground", self, width, height)
if (tab) then
-- manually paint active tab since messages handle their own alpha lifetime
surface.SetAlphaMultiplier(1)
tab:PaintManual()
surface.SetAlphaMultiplier(alpha / 255)
end
if (alpha > 0) then
hook.Run("PostChatboxDraw", width, height, self:GetAlpha())
end
end
-- get the command of the current chat class in the textentry if possible
function PANEL:GetTextEntryChatClass(text)
text = text or self.entry:GetText()
local chatType = ix.chat.Parse(LocalPlayer(), text, true)
if (chatType and chatType != "ic") then
-- OOC is the only one with two slashes as its prefix, so we'll make a special case for it here
if (chatType == "ooc") then
return "ooc"
end
local class = ix.chat.classes[chatType]
if (istable(class.prefix)) then
for _, v in ipairs(class.prefix) do
if (v:utf8sub(1, 1) == "/") then
return v:utf8sub(2):utf8lower()
end
end
elseif (class.prefix:utf8sub(1, 1) == "/") then
return class.prefix:utf8sub(2):utf8lower()
end
end
end
-- chatbox panel hooks
-- called when the textentry value changes
function PANEL:OnTextChanged(text)
hook.Run("ChatTextChanged", text)
local preview = self.preview
local autocomplete = self.autocomplete
local chatClassCommand = self:GetTextEntryChatClass(text)
self.prefix:SetText(hook.Run("GetChatPrefixInfo", text))
if (chatClassCommand) then
preview:SetCommand(chatClassCommand)
preview:SetVisible(true)
preview:UpdateArguments(text)
autocomplete:SetVisible(false)
return
end
local start, _, command = text:find("(/(%w+)%s)")
command = ix.command.list[tostring(command):utf8sub(2, tostring(command):utf8len() - 1):utf8lower()]
-- update preview if we've found a command
if (start == 1 and command) then
preview:SetCommand(command.uniqueID)
preview:SetVisible(true)
preview:UpdateArguments(text)
-- we don't need the autocomplete because we have a command already typed out
autocomplete:SetVisible(false)
return
-- if there's a slash then we're probably going to be (or are currently) typing out a command
elseif (text:utf8sub(1, 1) == "/") then
command = text:match("(/(%w+))") or "/"
preview:SetVisible(false) -- we don't have a valid command yet
autocomplete:Update(command:utf8sub(2))
autocomplete:SetVisible(true)
return
end
if (preview:GetCommand() != "") then
preview:SetCommand("")
preview:SetVisible(false)
end
if (autocomplete:IsVisible()) then
autocomplete:SetVisible(false)
end
end
DEFINE_BASECLASS("DTextEntry")
function PANEL:OnKeyCodeTyped(key)
if (key == KEY_TAB) then
if (self.autocomplete:IsOpen() and #self.autocomplete:GetCommands() > 0) then
local newText = self.autocomplete:SelectNext()
self.entry:SetText(newText)
self.entry:SetCaretPos(newText:utf8len())
end
return true
end
return BaseClass.OnKeyCodeTyped(self.entry, key)
end
-- called when player types something and presses enter in the textentry
function PANEL:OnMessageSent()
local text = self.entry:GetText()
if (text:find("%S")) then
local lastEntry = ix.chat.history[#ix.chat.history]
-- only add line to textentry history if it isn't the same message
if (lastEntry != text) then
if (#ix.chat.history >= 20) then
table.remove(ix.chat.history, 1)
end
ix.chat.history[#ix.chat.history + 1] = text
end
net.Start("ixChatMessage")
net.WriteString(text)
net.SendToServer()
end
self:SetActive(false) -- textentry is set to "" in SetActive
end
-- called when the player changes the currently active tab
function PANEL:OnTabChanged(panel)
panel:InvalidateLayout(true)
panel:ScrollToBottom()
end
-- called when the player creates a new tab
function PANEL:OnTabCreated(id, filter)
self.tabs:AddTab(id, filter)
PLUGIN:SaveTabs()
end
-- called when the player updates a tab's filter
function PANEL:OnTabUpdated(id, filter, newID)
local tab = self.tabs:GetTabs()[id]
if (!tab) then
return
end
tab:SetFilter(filter)
self.tabs:RenameTab(id, newID)
PLUGIN:SaveTabs()
end
-- called when a tab's button was right-clicked
function PANEL:OnTabRightClick(button, tab, id)
local menu = DermaMenu()
menu:AddOption(L("chatCustomize"), function()
if (IsValid(ix.gui.chatTabCustomize)) then
ix.gui.chatTabCustomize:Remove()
end
local panel = vgui.Create("ixChatboxTabCustomize")
panel:PopulateFromTab(id, tab:GetFilter())
panel.OnTabUpdated = ix.util.Bind(self, self.OnTabUpdated)
end)
menu:AddSpacer()
menu:AddOption(L("chatCloseTab"), function()
self.tabs:RemoveTab(id)
PLUGIN:SaveTabs()
end)
menu:Open()
menu:MakePopup() -- HACK: mouse input doesn't work when created immediately after opening chatbox
end
-- called when a message needs to be added to applicable tabs
function PANEL:AddMessage(...)
local class = CHAT_CLASS and CHAT_CLASS.uniqueID or "notice"
local activeTab = self.tabs:GetActiveTab()
-- track whether or not the message was filtered out in the active tab
local bShown = false
if (activeTab and !activeTab:GetFilter()[class]) then
activeTab:AddLine({...}, true)
bShown = true
end
for _, v in pairs(self.tabs:GetTabs()) do
if (v:GetID() == activeTab:GetID()) then
continue -- we already added it to the active tab
end
if (!v:GetFilter()[class]) then
v:AddLine({...}, true)
-- mark other tabs as unread if we didn't show the message in the active tab
if (!bShown) then
v:GetButton():SetUnread(true)
end
end
end
if (bShown) then
chat.PlaySound()
end
end
vgui.Register("ixChatbox", PANEL, "EditablePanel")
================================================
FILE: plugins/chatbox/derma/cl_chatboxcustomize.lua
================================================
local PLUGIN = PLUGIN
local PANEL = {}
function PANEL:Init()
ix.gui.chatTabCustomize = self
self:SetTitle(L("chatNewTab"))
self:SetSizable(true)
self:SetSize(ScrW() * 0.5, ScrH() * 0.5)
self.settings = self:Add("ixSettings")
self.settings:Dock(FILL)
self.settings:SetSearchEnabled(true)
self.settings:AddCategory(L("chatAllowedClasses"))
-- controls
local controlsPanel = self:Add("Panel")
controlsPanel:Dock(BOTTOM)
controlsPanel:DockMargin(0, 4, 0, 0)
controlsPanel:SetTall(32)
self.create = controlsPanel:Add("DButton")
self.create:SetText(L("create"))
self.create:SizeToContents()
self.create:Dock(FILL)
self.create:DockMargin(0, 0, 4, 0)
self.create.DoClick = ix.util.Bind(self, self.CreateClicked)
local uncheckAll = controlsPanel:Add("DButton")
uncheckAll:SetText(L("uncheckAll"))
uncheckAll:SizeToContents()
uncheckAll:Dock(RIGHT)
uncheckAll.DoClick = function()
self:SetAllValues(false)
end
local checkAll = controlsPanel:Add("DButton")
checkAll:SetText(L("checkAll"))
checkAll:SizeToContents()
checkAll:Dock(RIGHT)
checkAll:DockMargin(0, 0, 4, 0)
checkAll.DoClick = function()
self:SetAllValues(true)
end
-- chat class settings
self.name = self.settings:AddRow(ix.type.string)
self.name:SetText(L("chatTabName"))
self.name:SetValue(L("chatNewTabTitle"))
self.name:SetZPos(-1)
for k, _ in SortedPairs(ix.chat.classes) do
local panel = self.settings:AddRow(ix.type.bool, L("chatAllowedClasses"))
panel:SetText(k)
panel:SetValue(true, true)
end
self.settings:SizeToContents()
self:Center()
self:MakePopup()
end
function PANEL:PopulateFromTab(name, filter)
self.tab = name
self:SetTitle(L("chatCustomize"))
self.create:SetText(L("update"))
self.name:SetValue(name)
for _, v in ipairs(self.settings:GetRows()) do
if (filter[v:GetText()]) then
v:SetValue(false, true)
end
end
end
function PANEL:SetAllValues(bValue)
for _, v in ipairs(self.settings:GetRows()) do
if (v == self.name) then
continue
end
v:SetValue(tobool(bValue), true)
end
end
function PANEL:CreateClicked()
local name = self.tab and self.tab or self.name:GetValue()
if (self.tab != self.name:GetValue() and PLUGIN:TabExists(self.name:GetValue())) then
ix.util.Notify(L("chatTabExists"))
return
end
local filter = {}
for _, v in ipairs(self.settings:GetRows()) do
-- we only want to add entries for classes we don't want shown
if (!v:GetValue()) then
filter[v:GetText()] = true
end
end
if (self.tab) then
self:OnTabUpdated(name, filter, self.name:GetValue())
else
self:OnTabCreated(name, filter)
end
self:Remove()
end
function PANEL:OnTabCreated(id, filter)
end
function PANEL:OnTabUpdated(id, filter, newID)
end
vgui.Register("ixChatboxTabCustomize", PANEL, "DFrame")
================================================
FILE: plugins/chatbox/sh_plugin.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Chatbox"
PLUGIN.author = "`impulse"
PLUGIN.description = "Replaces the chatbox to enable customization, autocomplete, and useful info."
if (CLIENT) then
ix.chat.history = ix.chat.history or {} -- array of strings the player has entered into the chatbox
ix.chat.currentCommand = ""
ix.chat.currentArguments = {}
ix.option.Add("chatNotices", ix.type.bool, false, {
category = "chat"
})
ix.option.Add("chatTimestamps", ix.type.bool, false, {
category = "chat"
})
ix.option.Add("chatFontScale", ix.type.number, 1, {
category = "chat", min = 0.1, max = 2, decimals = 2,
OnChanged = function()
hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont"))
PLUGIN:CreateChat()
end
})
ix.option.Add("chatOutline", ix.type.bool, false, {
category = "chat"
})
-- tabs and their respective filters
ix.option.Add("chatTabs", ix.type.string, "", {
category = "chat",
hidden = function()
return true
end
})
-- chatbox size and position
ix.option.Add("chatPosition", ix.type.string, "", {
category = "chat",
hidden = function()
return true
end
})
function PLUGIN:CreateChat()
if (IsValid(self.panel)) then
self.panel:Remove()
end
self.panel = vgui.Create("ixChatbox")
self.panel:SetupTabs(util.JSONToTable(ix.option.Get("chatTabs", "")))
self.panel:SetupPosition(util.JSONToTable(ix.option.Get("chatPosition", "")))
hook.Run("ChatboxCreated")
end
function PLUGIN:TabExists(id)
if (!IsValid(self.panel)) then
return false
end
return self.panel.tabs:GetTabs()[id] != nil
end
function PLUGIN:SaveTabs()
local tabs = {}
for id, panel in pairs(self.panel.tabs:GetTabs()) do
tabs[id] = panel:GetFilter()
end
ix.option.Set("chatTabs", util.TableToJSON(tabs))
end
function PLUGIN:SavePosition()
local x, y = self.panel:GetPos()
local width, height = self.panel:GetSize()
ix.option.Set("chatPosition", util.TableToJSON({x, y, width, height}))
end
function PLUGIN:InitPostEntity()
self:CreateChat()
end
function PLUGIN:PlayerBindPress(client, bind, pressed)
bind = bind:lower()
if (bind:find("messagemode") and pressed) then
self.panel:SetActive(true)
return true
end
end
function PLUGIN:OnPauseMenuShow()
if (!IsValid(ix.gui.chat) or !ix.gui.chat:GetActive()) then
return
end
ix.gui.chat:SetActive(false)
return false
end
function PLUGIN:HUDShouldDraw(element)
if (element == "CHudChat") then
return false
end
end
function PLUGIN:ScreenResolutionChanged(oldWidth, oldHeight)
self:CreateChat()
end
function PLUGIN:ChatText(index, name, text, messageType)
if (messageType == "none" and IsValid(self.panel)) then
self.panel:AddMessage(text)
end
end
-- luacheck: globals chat
chat.ixAddText = chat.ixAddText or chat.AddText
function chat.AddText(...)
if (IsValid(PLUGIN.panel)) then
PLUGIN.panel:AddMessage(...)
end
-- log chat message to console
local text = {}
for _, v in ipairs({...}) do
if (istable(v) or isstring(v)) then
text[#text + 1] = v
elseif (isentity(v) and v:IsPlayer()) then
text[#text + 1] = team.GetColor(v:Team())
text[#text + 1] = v:Name()
elseif (type(v) != "IMaterial") then
text[#text + 1] = tostring(v)
end
end
text[#text + 1] = "\n"
MsgC(unpack(text))
end
else
util.AddNetworkString("ixChatMessage")
net.Receive("ixChatMessage", function(length, client)
local text = net.ReadString()
if ((client.ixNextChat or 0) < CurTime() and isstring(text) and text:find("%S")) then
local maxLength = ix.config.Get("chatMax")
if (text:utf8len() > maxLength) then
text = text:utf8sub(0, maxLength)
end
hook.Run("PlayerSay", client, text)
client.ixNextChat = CurTime() + 0.5
end
end)
end
================================================
FILE: plugins/containers/entities/entities/ix_container.lua
================================================
ENT.Type = "anim"
ENT.PrintName = "Container"
ENT.Category = "Helix"
ENT.Spawnable = false
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("Int", 0, "ID")
self:NetworkVar("Bool", 0, "Locked")
self:NetworkVar("String", 0, "DisplayName")
end
if (SERVER) then
function ENT:Initialize()
self:PhysicsInit(SOLID_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self.receivers = {}
local definition = ix.container.stored[self:GetModel():lower()]
if (definition) then
self:SetDisplayName(definition.name)
end
local physObj = self:GetPhysicsObject()
if (IsValid(physObj)) then
physObj:EnableMotion(true)
physObj:Wake()
end
end
function ENT:SetInventory(inventory)
if (inventory) then
self:SetID(inventory:GetID())
end
end
function ENT:SetMoney(amount)
self.money = math.max(0, math.Round(tonumber(amount) or 0))
end
function ENT:GetMoney()
return self.money or 0
end
function ENT:OnRemove()
local index = self:GetID()
if (!ix.shuttingDown and !self.ixIsSafe and ix.entityDataLoaded and index) then
local inventory = ix.item.inventories[index]
if (inventory) then
ix.item.inventories[index] = nil
local query = mysql:Delete("ix_items")
query:Where("inventory_id", index)
query:Execute()
query = mysql:Delete("ix_inventories")
query:Where("inventory_id", index)
query:Execute()
hook.Run("ContainerRemoved", self, inventory)
end
end
end
function ENT:OpenInventory(activator)
local inventory = self:GetInventory()
if (inventory) then
local name = self:GetDisplayName()
local definition = ix.container.stored[self:GetModel():lower()]
ix.storage.Open(activator, inventory, {
name = name,
entity = self,
searchTime = ix.config.Get("containerOpenTime", 0.7),
data = {money = self:GetMoney()},
OnPlayerOpen = function()
if (definition.OnOpen) then
definition.OnOpen(self, activator)
end
end,
OnPlayerClose = function()
if (definition.OnClose) then
definition.OnClose(self, activator)
end
ix.log.Add(activator, "closeContainer", name, inventory:GetID())
end
})
if (self:GetLocked()) then
self.Sessions[activator:GetCharacter():GetID()] = true
end
ix.log.Add(activator, "openContainer", name, inventory:GetID())
end
end
function ENT:Use(activator)
local inventory = self:GetInventory()
if (inventory and (activator.ixNextOpen or 0) < CurTime()) then
local character = activator:GetCharacter()
if (character) then
local definition = ix.container.stored[self:GetModel():lower()]
if (self:GetLocked() and !self.Sessions[character:GetID()]) then
self:EmitSound(definition.locksound or "doors/default_locked.wav")
if (!self.keypad) then
net.Start("ixContainerPassword")
net.WriteEntity(self)
net.Send(activator)
end
else
self:OpenInventory(activator)
end
end
activator.ixNextOpen = CurTime() + 1
end
end
else
ENT.PopulateEntityInfo = true
local COLOR_LOCKED = Color(200, 38, 19, 200)
local COLOR_UNLOCKED = Color(135, 211, 124, 200)
function ENT:OnPopulateEntityInfo(tooltip)
local definition = ix.container.stored[self:GetModel():lower()]
local bLocked = self:GetLocked()
surface.SetFont("ixIconsSmall")
local iconText = bLocked and "P" or "Q"
local iconWidth, iconHeight = surface.GetTextSize(iconText)
-- minimal tooltips have centered text so we'll draw the icon above the name instead
if (tooltip:IsMinimal()) then
local icon = tooltip:AddRow("icon")
icon:SetFont("ixIconsSmall")
icon:SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED)
icon:SetText(iconText)
icon:SizeToContents()
end
local title = tooltip:AddRow("name")
title:SetImportant()
title:SetText(self:GetDisplayName())
title:SetBackgroundColor(ix.config.Get("color"))
title:SetTextInset(iconWidth + 8, 0)
title:SizeToContents()
if (!tooltip:IsMinimal()) then
title.Paint = function(panel, width, height)
panel:PaintBackground(width, height)
surface.SetFont("ixIconsSmall")
surface.SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED)
surface.SetTextPos(4, height * 0.5 - iconHeight * 0.5)
surface.DrawText(iconText)
end
end
local description = tooltip:AddRow("description")
description:SetText(definition.description)
description:SizeToContents()
end
end
function ENT:GetInventory()
return ix.item.inventories[self:GetID()]
end
================================================
FILE: plugins/containers/sh_definitions.lua
================================================
--[[
ix.container.Register(model, {
name = "Crate",
description = "A simple wooden create.",
width = 4,
height = 4,
locksound = "",
opensound = ""
})
]]--
ix.container.Register("models/props_junk/wood_crate001a.mdl", {
name = "Crate",
description = "A simple wooden crate.",
width = 4,
height = 4,
})
ix.container.Register("models/props_c17/lockers001a.mdl", {
name = "Locker",
description = "A white locker.",
width = 3,
height = 5,
})
ix.container.Register("models/props_wasteland/controlroom_storagecloset001a.mdl", {
name = "Metal Cabinet",
description = "A green metal cabinet.",
width = 4,
height = 5,
})
ix.container.Register("models/props_wasteland/controlroom_storagecloset001b.mdl", {
name = "Metal Cabinet",
description = "A green metal cabinet.",
width = 4,
height = 5,
})
ix.container.Register("models/props_wasteland/controlroom_filecabinet001a.mdl", {
name = "File Cabinet",
description = "A metal filing cabinet.",
width = 5,
height = 3
})
ix.container.Register("models/props_wasteland/controlroom_filecabinet002a.mdl", {
name = "File Cabinet",
description = "A metal filing cabinet.",
width = 3,
height = 6,
})
ix.container.Register("models/props_lab/filecabinet02.mdl", {
name = "File Cabinet",
description = "A metal filing cabinet.",
width = 5,
height = 3
})
ix.container.Register("models/props_c17/furniturefridge001a.mdl", {
name = "Refrigerator",
description = "A metal box for keeping food in.",
width = 2,
height = 3,
})
ix.container.Register("models/props_wasteland/kitchen_fridge001a.mdl", {
name = "Large Refrigerator",
description = "A large metal box for storing even more food in.",
width = 4,
height = 5,
})
ix.container.Register("models/props_junk/trashbin01a.mdl", {
name = "Trash Bin",
description = "What do you expect to find in here?",
width = 2,
height = 2,
})
ix.container.Register("models/props_junk/trashdumpster01a.mdl", {
name = "Dumpster",
description = "A dumpster meant to stow away trash. It emanates an unpleasant smell.",
width = 6,
height = 3
})
ix.container.Register("models/items/ammocrate_smg1.mdl", {
name = "Ammo Crate",
description = "A heavy crate that stores ammo.",
width = 5,
height = 3,
OnOpen = function(entity, activator)
local closeSeq = entity:LookupSequence("Close")
entity:ResetSequence(closeSeq)
timer.Simple(2, function()
if (entity and IsValid(entity)) then
local openSeq = entity:LookupSequence("Open")
entity:ResetSequence(openSeq)
end
end)
end
})
ix.container.Register("models/props_forest/footlocker01_closed.mdl", {
name = "Footlocker",
description = "A small chest to store belongings in.",
width = 5,
height = 3
})
ix.container.Register("models/Items/item_item_crate.mdl", {
name = "Item Crate",
description = "A crate to store some belongings in.",
width = 5,
height = 3
})
ix.container.Register("models/props_c17/cashregister01a.mdl", {
name = "Cash Register",
description = "A register with some buttons and a drawer.",
width = 2,
height = 1
})
================================================
FILE: plugins/containers/sh_plugin.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Containers"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Provides the ability to store items."
ix.container = ix.container or {}
ix.container.stored = ix.container.stored or {}
ix.config.Add("containerSave", true, "Whether or not containers will save after a server restart.", nil, {
category = "Containers"
})
ix.config.Add("containerOpenTime", 0.7, "How long it takes to open a container.", nil, {
data = {min = 0, max = 50},
category = "Containers"
})
function ix.container.Register(model, data)
ix.container.stored[model:lower()] = data
end
ix.util.Include("sh_definitions.lua")
if (SERVER) then
util.AddNetworkString("ixContainerPassword")
function PLUGIN:PlayerSpawnedProp(client, model, entity)
model = tostring(model):lower()
local data = ix.container.stored[model]
if (data) then
if (hook.Run("CanPlayerSpawnContainer", client, model, entity) == false) then return end
local container = ents.Create("ix_container")
container:SetPos(entity:GetPos())
container:SetAngles(entity:GetAngles())
container:SetModel(model)
container:Spawn()
ix.inventory.New(0, "container:" .. model:lower(), function(inventory)
-- we'll technically call this a bag since we don't want other bags to go inside
inventory.vars.isBag = true
inventory.vars.isContainer = true
if (IsValid(container)) then
container:SetInventory(inventory)
self:SaveContainer()
end
end)
entity:Remove()
end
end
function PLUGIN:CanSaveContainer(entity, inventory)
return ix.config.Get("containerSave", true)
end
function PLUGIN:SaveContainer()
local data = {}
for _, v in ipairs(ents.FindByClass("ix_container")) do
if (hook.Run("CanSaveContainer", v, v:GetInventory()) != false) then
local inventory = v:GetInventory()
if (inventory) then
data[#data + 1] = {
v:GetPos(),
v:GetAngles(),
inventory:GetID(),
v:GetModel(),
v.password,
v:GetDisplayName(),
v:GetMoney()
}
end
else
local index = v:GetID()
local query = mysql:Delete("ix_items")
query:Where("inventory_id", index)
query:Execute()
query = mysql:Delete("ix_inventories")
query:Where("inventory_id", index)
query:Execute()
end
end
self:SetData(data)
end
function PLUGIN:SaveData()
if (!ix.shuttingDown) then
self:SaveContainer()
end
end
function PLUGIN:ContainerRemoved(entity, inventory)
self:SaveContainer()
end
function PLUGIN:LoadData()
local data = self:GetData()
if (data) then
for _, v in ipairs(data) do
local data2 = ix.container.stored[v[4]:lower()]
if (data2) then
local inventoryID = tonumber(v[3])
if (!inventoryID or inventoryID < 1) then
ErrorNoHalt(string.format(
"[Helix] Attempted to restore container inventory with invalid inventory ID '%s' (%s, %s)\n",
tostring(inventoryID), v[6] or "no name", v[4] or "no model"))
continue
end
local entity = ents.Create("ix_container")
entity:SetPos(v[1])
entity:SetAngles(v[2])
entity:Spawn()
entity:SetModel(v[4])
entity:SetSolid(SOLID_VPHYSICS)
entity:PhysicsInit(SOLID_VPHYSICS)
if (v[5]) then
entity.password = v[5]
entity:SetLocked(true)
entity.Sessions = {}
entity.PasswordAttempts = {}
end
if (v[6]) then
entity:SetDisplayName(v[6])
end
if (v[7]) then
entity:SetMoney(v[7])
end
ix.inventory.Restore(inventoryID, data2.width, data2.height, function(inventory)
inventory.vars.isBag = true
inventory.vars.isContainer = true
if (IsValid(entity)) then
entity:SetInventory(inventory)
end
end)
local physObject = entity:GetPhysicsObject()
if (IsValid(physObject)) then
physObject:EnableMotion()
end
end
end
end
end
net.Receive("ixContainerPassword", function(length, client)
if ((client.ixNextContainerPassword or 0) > RealTime()) then
return
end
local entity = net.ReadEntity()
local steamID = client:SteamID()
local attempts = entity.PasswordAttempts[steamID]
if (attempts and attempts >= 10) then
client:NotifyLocalized("passwordAttemptLimit")
return
end
local password = net.ReadString()
local dist = entity:GetPos():DistToSqr(client:GetPos())
if (dist < 16384 and password) then
if (entity.password and entity.password == password) then
entity:OpenInventory(client)
else
entity.PasswordAttempts[steamID] = attempts and attempts + 1 or 1
client:NotifyLocalized("wrongPassword")
end
end
client.ixNextContainerPassword = RealTime() + 1
end)
ix.log.AddType("containerPassword", function(client, ...)
local arg = {...}
return string.format("%s has %s the password for '%s'.", client:Name(), arg[3] and "set" or "removed", arg[1], arg[2])
end)
ix.log.AddType("containerName", function(client, ...)
local arg = {...}
if (arg[3]) then
return string.format("%s has set container %d name to '%s'.", client:Name(), arg[2], arg[1])
else
return string.format("%s has removed container %d name.", client:Name(), arg[2])
end
end)
ix.log.AddType("openContainer", function(client, ...)
local arg = {...}
return string.format("%s opened the '%s' #%d container.", client:Name(), arg[1], arg[2])
end, FLAG_NORMAL)
ix.log.AddType("closeContainer", function(client, ...)
local arg = {...}
return string.format("%s closed the '%s' #%d container.", client:Name(), arg[1], arg[2])
end, FLAG_NORMAL)
else
net.Receive("ixContainerPassword", function(length)
local entity = net.ReadEntity()
Derma_StringRequest(
L("containerPasswordWrite"),
L("containerPasswordWrite"),
"",
function(val)
net.Start("ixContainerPassword")
net.WriteEntity(entity)
net.WriteString(val)
net.SendToServer()
end
)
end)
end
function PLUGIN:InitializedPlugins()
for k, v in pairs(ix.container.stored) do
if (v.name and v.width and v.height) then
ix.inventory.Register("container:" .. k:lower(), v.width, v.height)
else
ErrorNoHalt("[Helix] Container for '"..k.."' is missing all inventory information!\n")
ix.container.stored[k] = nil
end
end
end
-- properties
properties.Add("container_setpassword", {
MenuLabel = "Set Password",
Order = 400,
MenuIcon = "icon16/lock_edit.png",
Filter = function(self, entity, client)
if (entity:GetClass() != "ix_container") then return false end
if (!gamemode.Call("CanProperty", client, "container_setpassword", entity)) then return false end
return true
end,
Action = function(self, entity)
Derma_StringRequest(L("containerPasswordWrite"), "", "", function(text)
self:MsgStart()
net.WriteEntity(entity)
net.WriteString(text)
self:MsgEnd()
end)
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
local password = net.ReadString()
entity.Sessions = {}
entity.PasswordAttempts = {}
if (password:len() != 0) then
entity:SetLocked(true)
entity.password = password
client:NotifyLocalized("containerPassword", password)
else
entity:SetLocked(false)
entity.password = nil
client:NotifyLocalized("containerPasswordRemove")
end
local name = entity:GetDisplayName()
local inventory = entity:GetInventory()
ix.log.Add(client, "containerPassword", name, inventory:GetID(), password:len() != 0)
end
})
properties.Add("container_setname", {
MenuLabel = "Set Name",
Order = 400,
MenuIcon = "icon16/tag_blue_edit.png",
Filter = function(self, entity, client)
if (entity:GetClass() != "ix_container") then return false end
if (!gamemode.Call("CanProperty", client, "container_setname", entity)) then return false end
return true
end,
Action = function(self, entity)
Derma_StringRequest(L("containerNameWrite"), "", "", function(text)
self:MsgStart()
net.WriteEntity(entity)
net.WriteString(text)
self:MsgEnd()
end)
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
local name = net.ReadString()
if (name:len() != 0) then
entity:SetDisplayName(name)
client:NotifyLocalized("containerName", name)
else
local definition = ix.container.stored[entity:GetModel():lower()]
entity:SetDisplayName(definition.name)
client:NotifyLocalized("containerNameRemove")
end
local inventory = entity:GetInventory()
ix.log.Add(client, "containerName", name, inventory:GetID(), name:len() != 0)
end
})
================================================
FILE: plugins/crosshair.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Crosshair"
PLUGIN.author = "Black Tea"
PLUGIN.description = "A Crosshair."
if (CLIENT) then
local function drawdot( pos, size, col )
local color = col[2]
surface.SetDrawColor(color.r, color.g, color.b, color.a)
surface.DrawRect(pos[1] - size/2, pos[2] - size/2, size, size)
color = col[1]
surface.SetDrawColor(color.r, color.g, color.b, color.a)
surface.DrawOutlinedRect(pos[1] - size/2, pos[2] - size/2 , size, size)
end
local aimVector, punchAngle, ft, screen, scaleFraction, distance
local math_round = math.Round
local curGap = 0
local curAlpha = 0
local maxDistance = 1000 ^ 2
local crossSize = 4
local crossGap = 0
local colors = {color_black}
local filter = {}
function PLUGIN:DrawCrosshair(x, y, trace)
local entity = trace.Entity
distance = trace.StartPos:DistToSqr(trace.HitPos)
scaleFraction = 1 - math.Clamp(distance / maxDistance, 0, .5)
crossSize = 4
crossGap = 25 * (scaleFraction - (LocalPlayer():IsWepRaised() and 0 or .1))
if (IsValid(entity) and entity:GetClass() == "ix_item" and
entity:GetPos():DistToSqr(trace.StartPos) <= 16384) then
crossGap = 0
crossSize = 5
end
curGap = Lerp(ft * 2, curGap, crossGap)
curAlpha = Lerp(ft * 2, curAlpha, !LocalPlayer():IsWepRaised() and 255 or 150)
curAlpha = hook.Run("GetCrosshairAlpha", curAlpha) or curAlpha
colors[2] = Color(255, curAlpha, curAlpha, curAlpha)
if (curAlpha > 1) then
drawdot( {math_round(screen.x), math_round(screen.y)}, crossSize, colors)
drawdot( {math_round(screen.x + curGap), math_round(screen.y)}, crossSize, colors)
drawdot( {math_round(screen.x - curGap), math_round(screen.y)}, crossSize, colors)
drawdot( {math_round(screen.x), math_round(screen.y + curGap * .8)}, crossSize, colors)
drawdot( {math_round(screen.x), math_round(screen.y - curGap * .8)}, crossSize, colors)
end
end
-- luacheck: globals g_ContextMenu
function PLUGIN:PostDrawHUD()
local client = LocalPlayer()
if (!client:GetCharacter() or !client:Alive()) then
return
end
local entity = Entity(client:GetLocalVar("ragdoll", 0))
if (entity:IsValid()) then
return
end
local wep = client:GetActiveWeapon()
local bShouldDraw = hook.Run("ShouldDrawCrosshair", client, wep)
if (bShouldDraw == false or !IsValid(wep) or wep.DrawCrosshair == false) then
return
end
if (bShouldDraw == false or g_ContextMenu:IsVisible() or
(IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing())) then
return
end
aimVector = client:EyeAngles()
punchAngle = client:GetViewPunchAngles()
ft = FrameTime()
filter = {client}
local vehicle = client:GetVehicle()
if (vehicle and IsValid(vehicle)) then
aimVector = aimVector + vehicle:GetAngles()
table.insert(filter, vehicle)
end
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + (aimVector + punchAngle):Forward() * 65535
data.filter = filter
local trace = util.TraceLine(data)
local drawTarget = self
local drawFunction = self.DrawCrosshair
-- we'll manually call this since CHudCrosshair is never drawn; checks are already performed
if (wep.DoDrawCrosshair) then
drawTarget = wep
drawFunction = wep.DoDrawCrosshair
end
screen = trace.HitPos:ToScreen()
drawFunction(drawTarget, screen.x, screen.y, trace)
end
end
================================================
FILE: plugins/doors/cl_plugin.lua
================================================
-- luacheck: globals ACCESS_LABELS
ACCESS_LABELS = {}
ACCESS_LABELS[DOOR_OWNER] = "owner"
ACCESS_LABELS[DOOR_TENANT] = "tenant"
ACCESS_LABELS[DOOR_GUEST] = "guest"
ACCESS_LABELS[DOOR_NONE] = "none"
function PLUGIN:GetDefaultDoorInfo(door)
local owner = IsValid(door:GetDTEntity(0)) and door:GetDTEntity(0) or nil
local name = door:GetNetVar("title", door:GetNetVar("name", IsValid(owner) and L"dTitleOwned" or L"dTitle"))
local description = door:GetNetVar("ownable") and L("dIsOwnable") or L("dIsNotOwnable")
local color = ix.config.Get("color")
local faction = door:GetNetVar("faction")
local class = door:GetNetVar("class")
if (class) then
local classData = ix.class.list[class]
if (classData) then
if (classData.color) then
color = classData.color
end
if (!owner) then
description = L("dOwnedBy", L2(classData.name) or classData.name)
end
end
elseif (faction) then
local info = ix.faction.indices[faction]
color = team.GetColor(faction)
if (info and !owner) then
description = L("dOwnedBy", L2(info.name) or info.name)
end
end
if (owner) then
description = L("dOwnedBy", owner:GetName())
end
return {
name = name,
description = description,
color = color
}
end
function PLUGIN:DrawDoorInfo(door, width, position, angles, scale, clientPosition)
local alpha = math.max((1 - clientPosition:DistToSqr(door:GetPos()) / 65536) * 255, 0)
if (alpha < 1) then
return
end
local info = hook.Run("GetDoorInfo", door) or self:GetDefaultDoorInfo(door)
if (!istable(info) or table.IsEmpty(info)) then
return
end
-- title + background
surface.SetFont("ix3D2DMediumFont")
local nameWidth, nameHeight = surface.GetTextSize(info.name)
derma.SkinFunc("DrawImportantBackground", -width * 0.5, -nameHeight * 0.5,
width, nameHeight, ColorAlpha(info.color, alpha * 0.5))
surface.SetTextColor(ColorAlpha(color_white, alpha))
surface.SetTextPos(-nameWidth * 0.5, -nameHeight * 0.5)
surface.DrawText(info.name)
-- description
local lines = ix.util.WrapText(info.description, width, "ix3D2DSmallFont")
local y = nameHeight * 0.5 + 4
for i = 1, #lines do
local line = lines[i]
local textWidth, textHeight = surface.GetTextSize(line)
surface.SetTextPos(-textWidth * 0.5, y)
surface.DrawText(line)
y = y + textHeight
end
-- background blur
ix.util.PushBlur(function()
cam.Start3D2D(position, angles, scale)
surface.SetDrawColor(11, 11, 11, math.max(alpha - 100, 0))
surface.DrawRect(-width * 0.5, -nameHeight * 0.5, width, y + nameHeight * 0.5 + 4)
cam.End3D2D()
end)
end
function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox)
if (bDepth or bSkybox or !LocalPlayer():GetCharacter()) then
return
end
local entities = ents.FindInSphere(EyePos(), 256)
local clientPosition = LocalPlayer():GetPos()
for _, v in ipairs(entities) do
if (!IsValid(v) or !v:IsDoor() or !v:GetNetVar("visible")) then
continue
end
local color = v:GetColor()
if (v:IsEffectActive(EF_NODRAW) or color.a <= 0) then
continue
end
local position = v:LocalToWorld(v:OBBCenter())
local mins, maxs = v:GetCollisionBounds()
local width = 0
local size = maxs - mins
local trace = {
collisiongroup = COLLISION_GROUP_WORLD,
ignoreworld = true,
endpos = position
}
-- trace from shortest side to center to get correct position for rendering
if (size.z < size.x and size.z < size.y) then
trace.start = position - v:GetUp() * size.z
width = size.y
elseif (size.x < size.y) then
trace.start = position - v:GetForward() * size.x
width = size.y
elseif (size.y < size.x) then
trace.start = position - v:GetRight() * size.y
width = size.x
end
width = math.max(width, 12)
trace = util.TraceLine(trace)
local angles = trace.HitNormal:Angle()
local anglesOpposite = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Forward(), 90)
angles:RotateAroundAxis(angles:Right(), 90)
anglesOpposite:RotateAroundAxis(anglesOpposite:Forward(), 90)
anglesOpposite:RotateAroundAxis(anglesOpposite:Right(), -90)
local positionFront = trace.HitPos - (((position - trace.HitPos):Length() * 2) + 1) * trace.HitNormal
local positionOpposite = trace.HitPos + (trace.HitNormal * 2)
if (trace.HitNormal:Dot((clientPosition - position):GetNormalized()) < 0) then
-- draw front
cam.Start3D2D(positionFront, angles, 0.1)
self:DrawDoorInfo(v, width * 8, positionFront, angles, 0.1, clientPosition)
cam.End3D2D()
else
-- draw back
cam.Start3D2D(positionOpposite, anglesOpposite, 0.1)
self:DrawDoorInfo(v, width * 8, positionOpposite, anglesOpposite, 0.1, clientPosition)
cam.End3D2D()
end
end
end
net.Receive("ixDoorMenu", function()
if (IsValid(ix.gui.door)) then
return ix.gui.door:Remove()
end
local door = net.ReadEntity()
local access = net.ReadTable()
if (IsValid(door) and !table.IsEmpty(access)) then
local entity = net.ReadEntity()
ix.gui.door = vgui.Create("ixDoorMenu")
ix.gui.door:SetDoor(door, access, entity)
end
end)
net.Receive("ixDoorPermission", function()
local door = net.ReadEntity()
if (!IsValid(door)) then
return
end
local target = net.ReadEntity()
local access = net.ReadUInt(4)
local panel = door.ixPanel
if (IsValid(panel) and IsValid(target)) then
panel.access[target] = access
for _, v in ipairs(panel.access:GetLines()) do
if (v.player == target) then
v:SetColumnText(2, L(ACCESS_LABELS[access or 0]))
return
end
end
end
end)
================================================
FILE: plugins/doors/derma/cl_door.lua
================================================
local PANEL = {}
local function DoorSetPermission(door, target, permission)
net.Start("ixDoorPermission")
net.WriteEntity(door)
net.WriteEntity(target)
net.WriteUInt(permission, 4)
net.SendToServer()
end
function PANEL:Init()
self:SetSize(280, 240)
self:SetTitle(L"doorSettings")
self:Center()
self:MakePopup()
self.access = self:Add("DListView")
self.access:Dock(FILL)
self.access:AddColumn(L"name").Header:SetTextColor(Color(25, 25, 25))
self.access:AddColumn(L"access").Header:SetTextColor(Color(25, 25, 25))
self.access.OnClickLine = function(this, line, selected)
if (IsValid(line.player)) then
local menu = DermaMenu()
menu:AddOption(L"tenant", function()
if (self.accessData and self.accessData[line.player] != DOOR_TENANT) then
DoorSetPermission(self.door, line.player, DOOR_TENANT)
end
end):SetImage("icon16/user_add.png")
menu:AddOption(L"guest", function()
if (self.accessData and self.accessData[line.player] != DOOR_GUEST) then
DoorSetPermission(self.door, line.player, DOOR_GUEST)
end
end):SetImage("icon16/user_green.png")
menu:AddOption(L"none", function()
if (self.accessData and self.accessData[line.player] != DOOR_NONE) then
DoorSetPermission(self.door, line.player, DOOR_NONE)
end
end):SetImage("icon16/user_red.png")
menu:Open()
end
end
end
function PANEL:SetDoor(door, access, door2)
door.ixPanel = self
self.accessData = access
self.door = door
for _, v in player.Iterator() do
if (v != LocalPlayer() and v:GetCharacter()) then
self.access:AddLine(v:Name(), L(ACCESS_LABELS[access[v] or 0])).player = v
end
end
if (self:CheckAccess(DOOR_OWNER)) then
self.sell = self:Add("DButton")
self.sell:Dock(BOTTOM)
self.sell:SetText(L"sell")
self.sell:SetTextColor(color_white)
self.sell:DockMargin(0, 5, 0, 0)
self.sell.DoClick = function(this)
self:Remove()
ix.command.Send("doorsell")
end
end
if (self:CheckAccess(DOOR_TENANT)) then
self.name = self:Add("DTextEntry")
self.name:Dock(TOP)
self.name:DockMargin(0, 0, 0, 5)
self.name.Think = function(this)
if (!this:IsEditing()) then
local entity = IsValid(door2) and door2 or door
self.name:SetText(entity:GetNetVar("title", L"dTitleOwned"))
end
end
self.name.OnEnter = function(this)
ix.command.Send("doorsettitle", this:GetText())
end
end
end
function PANEL:CheckAccess(access)
access = access or DOOR_GUEST
if ((self.accessData[LocalPlayer()] or 0) >= access) then
return true
end
return false
end
function PANEL:Think()
if (self.accessData and !IsValid(self.door) and self:CheckAccess()) then
self:Remove()
end
end
vgui.Register("ixDoorMenu", PANEL, "DFrame")
================================================
FILE: plugins/doors/entities/weapons/ix_keys.lua
================================================
AddCSLuaFile()
if (CLIENT) then
SWEP.PrintName = "Keys"
SWEP.Slot = 0
SWEP.SlotPos = 2
SWEP.DrawAmmo = false
SWEP.DrawCrosshair = false
end
SWEP.Author = "Chessnut"
SWEP.Instructions = "Primary Fire: Lock\nSecondary Fire: Unlock"
SWEP.Purpose = "Hitting things and knocking on doors."
SWEP.Drop = false
SWEP.ViewModelFOV = 45
SWEP.ViewModelFlip = false
SWEP.AnimPrefix = "rpg"
SWEP.ViewTranslation = 4
SWEP.Primary.ClipSize = -1
SWEP.Primary.DefaultClip = -1
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = ""
SWEP.Primary.Damage = 5
SWEP.Primary.Delay = 0.75
SWEP.Secondary.ClipSize = -1
SWEP.Secondary.DefaultClip = 0
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = ""
SWEP.ViewModel = Model("models/weapons/c_arms_animations.mdl")
SWEP.WorldModel = ""
SWEP.UseHands = false
SWEP.LowerAngles = Angle(0, 5, -14)
SWEP.LowerAngles2 = Angle(0, 5, -22)
SWEP.IsAlwaysLowered = true
SWEP.FireWhenLowered = true
SWEP.HoldType = "passive"
-- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER
ACT_VM_FISTS_DRAW = 2
ACT_VM_FISTS_HOLSTER = 1
function SWEP:Holster()
if (!IsValid(self.Owner)) then
return
end
local viewModel = self.Owner:GetViewModel()
if (IsValid(viewModel)) then
viewModel:SetPlaybackRate(1)
viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER)
end
return true
end
function SWEP:Precache()
end
function SWEP:Initialize()
self:SetHoldType(self.HoldType)
end
function SWEP:PrimaryAttack()
local time = ix.config.Get("doorLockTime", 1)
local time2 = math.max(time, 1)
self:SetNextPrimaryFire(CurTime() + time2)
self:SetNextSecondaryFire(CurTime() + time2)
if (!IsFirstTimePredicted()) then
return
end
if (CLIENT) then
return
end
local data = {}
data.start = self.Owner:GetShootPos()
data.endpos = data.start + self.Owner:GetAimVector()*96
data.filter = self.Owner
local entity = util.TraceLine(data).Entity
--[[
Locks the entity if the contiditon fits:
1. The entity is door and client has access to the door.
2. The entity is vehicle and the "owner" variable is same as client's character ID.
--]]
if (IsValid(entity) and
(
(entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or
(entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner)
)
) then
self.Owner:SetAction("@locking", time, function()
self:ToggleLock(entity, true)
end)
return
end
end
function SWEP:ToggleLock(door, state)
if (IsValid(self.Owner) and self.Owner:GetPos():Distance(door:GetPos()) > 96) then
return
end
if (door:IsDoor()) then
local partner = door:GetDoorPartner()
if (state) then
if (IsValid(partner)) then
partner:Fire("lock")
end
door:Fire("lock")
self.Owner:EmitSound("doors/door_latch3.wav")
hook.Run("PlayerLockedDoor", self.Owner, door, partner)
else
if (IsValid(partner)) then
partner:Fire("unlock")
end
door:Fire("unlock")
self.Owner:EmitSound("doors/door_latch1.wav")
hook.Run("PlayerUnlockedDoor", self.Owner, door, partner)
end
elseif (door:IsVehicle()) then
if (state) then
door:Fire("lock")
if (door.IsSimfphyscar) then
door.IsLocked = true
end
self.Owner:EmitSound("doors/door_latch3.wav")
hook.Run("PlayerLockedVehicle", self.Owner, door)
else
door:Fire("unlock")
if (door.IsSimfphyscar) then
door.IsLocked = nil
end
self.Owner:EmitSound("doors/door_latch1.wav")
hook.Run("PlayerUnlockedVehicle", self.Owner, door)
end
end
end
function SWEP:SecondaryAttack()
local time = ix.config.Get("doorLockTime", 1)
local time2 = math.max(time, 1)
self:SetNextPrimaryFire(CurTime() + time2)
self:SetNextSecondaryFire(CurTime() + time2)
if (!IsFirstTimePredicted()) then
return
end
if (CLIENT) then
return
end
local data = {}
data.start = self.Owner:GetShootPos()
data.endpos = data.start + self.Owner:GetAimVector()*96
data.filter = self.Owner
local entity = util.TraceLine(data).Entity
--[[
Unlocks the entity if the contiditon fits:
1. The entity is door and client has access to the door.
2. The entity is vehicle and the "owner" variable is same as client's character ID.
]]--
if (IsValid(entity) and
(
(entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or
(entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner)
)
) then
self.Owner:SetAction("@unlocking", time, function()
self:ToggleLock(entity, false)
end)
return
end
end
================================================
FILE: plugins/doors/sh_commands.lua
================================================
local PLUGIN = PLUGIN
ix.command.Add("DoorSell", {
description = "@cmdDoorSell",
OnRun = function(self, client, arguments)
-- Get the entity 96 units infront of the player.
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
-- Check if the entity is a valid door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Check if the player owners the door.
if (client == entity:GetDTEntity(0)) then
entity = IsValid(entity.ixParent) and entity.ixParent or entity
-- Get the price that the door is sold for.
local price = math.Round(entity:GetNetVar("price", ix.config.Get("doorCost")) * ix.config.Get("doorSellRatio"))
local character = client:GetCharacter()
-- Remove old door information.
entity:RemoveDoorAccessData()
local doors = character:GetVar("doors") or {}
for k, v in ipairs(doors) do
if (v == entity) then
table.remove(doors, k)
end
end
character:SetVar("doors", doors, true)
-- Take their money and notify them.
character:GiveMoney(price)
hook.Run("OnPlayerPurchaseDoor", client, entity, false, PLUGIN.CallOnDoorChildren)
ix.log.Add(client, "selldoor")
return "@dSold", ix.currency.Get(price)
else
-- Otherwise tell them they can not.
return "@notOwner"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorBuy", {
description = "@cmdDoorBuy",
OnRun = function(self, client, arguments)
-- Get the entity 96 units infront of the player.
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
-- Check if the entity is a valid door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (!entity:GetNetVar("ownable") or entity:GetNetVar("faction") or entity:GetNetVar("class")) then
return "@dNotAllowedToOwn"
end
if (IsValid(entity:GetDTEntity(0))) then
return "@dOwnedBy", entity:GetDTEntity(0):Name()
end
entity = IsValid(entity.ixParent) and entity.ixParent or entity
-- Get the price that the door is bought for.
local price = entity:GetNetVar("price", ix.config.Get("doorCost"))
local character = client:GetCharacter()
-- Check if the player can actually afford it.
if (character:HasMoney(price)) then
-- Set the door to be owned by this player.
entity:SetDTEntity(0, client)
entity.ixAccess = {
[client] = DOOR_OWNER
}
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetDTEntity(0, client)
end)
local doors = character:GetVar("doors") or {}
doors[#doors + 1] = entity
character:SetVar("doors", doors, true)
-- Take their money and notify them.
character:TakeMoney(price)
hook.Run("OnPlayerPurchaseDoor", client, entity, true, PLUGIN.CallOnDoorChildren)
ix.log.Add(client, "buydoor")
return "@dPurchased", ix.currency.Get(price)
else
-- Otherwise tell them they can not.
return "@canNotAfford"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetUnownable", {
description = "@cmdDoorSetUnownable",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.text,
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Set it so it is unownable.
entity:SetNetVar("ownable", nil)
-- Change the name of the door if needed.
if (name:find("%S")) then
entity:SetNetVar("name", name)
end
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("ownable", nil)
if (name:find("%S")) then
child:SetNetVar("name", name)
end
end)
-- Save the door information.
PLUGIN:SaveDoorData()
return "@dMadeUnownable"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetOwnable", {
description = "@cmdDoorSetOwnable",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.text,
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Set it so it is ownable.
entity:SetNetVar("ownable", true)
entity:SetNetVar("visible", true)
-- Update the name.
if (name:find("%S")) then
entity:SetNetVar("name", name)
end
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("ownable", true)
child:SetNetVar("visible", true)
if (name:find("%S")) then
child:SetNetVar("name", name)
end
end)
-- Save the door information.
PLUGIN:SaveDoorData()
return "@dMadeOwnable"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetFaction", {
description = "@cmdDoorSetFaction",
privilege = "Manage Doors",
adminOnly = true,
arguments = bit.bor(ix.type.text, ix.type.optional),
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (!name or name == "") then
entity.ixFactionID = nil
entity:SetNetVar("faction", nil)
PLUGIN:CallOnDoorChildren(entity, function()
entity.ixFactionID = nil
entity:SetNetVar("faction", nil)
end)
PLUGIN:SaveDoorData()
return "@dRemoveFaction"
end
local faction
-- Loop through each faction, checking the uniqueID and name.
for k, v in pairs(ix.faction.teams) do
if (ix.util.StringMatches(k, name) or ix.util.StringMatches(L(v.name, client), name)) then
-- This faction matches the provided string.
faction = v
-- Escape the loop.
break
end
end
-- Check if a faction was found.
if (faction) then
entity.ixFactionID = faction.uniqueID
entity:SetNetVar("faction", faction.index)
PLUGIN:CallOnDoorChildren(entity, function()
entity.ixFactionID = faction.uniqueID
entity:SetNetVar("faction", faction.index)
end)
PLUGIN:SaveDoorData()
return "@dSetFaction", L(faction.name, client)
-- The faction was not found.
else
return "@invalidFaction"
end
end
end
})
ix.command.Add("DoorSetDisabled", {
description = "@cmdDoorSetDisabled",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.bool,
OnRun = function(self, client, bDisabled)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor()) then
-- Set it so it is ownable.
entity:SetNetVar("disabled", bDisabled)
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("disabled", bDisabled)
end)
PLUGIN:SaveDoorData()
-- Tell the player they have made the door (un)disabled.
return "@dSet" .. (bDisabled and "" or "Not") .. "Disabled"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetTitle", {
description = "@cmdDoorSetTitle",
arguments = ix.type.text,
OnRun = function(self, client, name)
-- Get the door infront of the player.
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
-- Validate the door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Make sure the name contains actual characters.
if (!name:find("%S")) then
return "@invalidArg", 1
end
--[[
NOTE: Here, we are setting two different networked names.
The title is a temporary name, while the other name is the
default name for the door. The reason for this is so when the
server closes while someone owns the door, it doesn't save THEIR
title, which could lead to unwanted things.
--]]
name = name:utf8sub(1, 24)
-- Check if they are allowed to change the door's name.
if (entity:CheckDoorAccess(client, DOOR_TENANT)) then
entity:SetNetVar("title", name)
elseif (CAMI.PlayerHasAccess(client, "Helix - Manage Doors", nil)) then
entity:SetNetVar("name", name)
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("name", name)
end)
else
-- Otherwise notify the player he/she can't.
return "@notOwner"
end
else
-- Notification of the door not being valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetParent", {
description = "@cmdDoorSetParent",
privilege = "Manage Doors",
adminOnly = true,
OnRun = function(self, client, arguments)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
client.ixDoorParent = entity
return "@dSetParentDoor"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetChild", {
description = "@cmdDoorSetChild",
privilege = "Manage Doors",
adminOnly = true,
OnRun = function(self, client, arguments)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (client.ixDoorParent == entity) then
return "@dCanNotSetAsChild"
end
-- Check if the player has set a door as a parent.
if (IsValid(client.ixDoorParent)) then
-- Add the door to the parent's list of children.
client.ixDoorParent.ixChildren = client.ixDoorParent.ixChildren or {}
client.ixDoorParent.ixChildren[entity:MapCreationID()] = true
-- Set the door's parent to the parent.
entity.ixParent = client.ixDoorParent
-- Save the door information.
PLUGIN:SaveDoorData()
PLUGIN:CopyParentDoor(entity)
return "@dAddChildDoor"
else
-- Tell the player they do not have a door parent.
return "@dNoParentDoor"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorRemoveChild", {
description = "@cmdDoorRemoveChild",
privilege = "Manage Doors",
adminOnly = true,
OnRun = function(self, client, arguments)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (client.ixDoorParent == entity) then
PLUGIN:CallOnDoorChildren(entity, function(child)
child.ixParent = nil
end)
entity.ixChildren = nil
return "@dRemoveChildren"
end
-- Check if the player has set a door as a parent.
if (IsValid(entity.ixParent) and entity.ixParent.ixChildren) then
-- Remove the door from the list of children.
entity.ixParent.ixChildren[entity:MapCreationID()] = nil
-- Remove the variable for the parent.
entity.ixParent = nil
PLUGIN:SaveDoorData()
return "@dRemoveChildDoor"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetHidden", {
description = "@cmdDoorSetHidden",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.bool,
OnRun = function(self, client, bHidden)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor()) then
entity:SetNetVar("visible", !bHidden)
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("visible", !bHidden)
end)
PLUGIN:SaveDoorData()
-- Tell the player they have made the door (un)hidden.
return "@dSet" .. (bHidden and "" or "Not") .. "Hidden"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetClass", {
description = "@cmdDoorSetClass",
privilege = "Manage Doors",
adminOnly = true,
arguments = bit.bor(ix.type.text, ix.type.optional),
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (!name or name == "") then
entity:SetNetVar("class", nil)
PLUGIN:CallOnDoorChildren(entity, function()
entity:SetNetVar("class", nil)
end)
PLUGIN:SaveDoorData()
return "@dRemoveClass"
end
local class, classData
for k, v in pairs(ix.class.list) do
if (ix.util.StringMatches(v.name, name) or ix.util.StringMatches(L(v.name, client), name)) then
class, classData = k, v
break
end
end
-- Check if a faction was found.
if (class) then
entity.ixClassID = class
entity:SetNetVar("class", class)
PLUGIN:CallOnDoorChildren(entity, function()
entity.ixClassID = class
entity:SetNetVar("class", class)
end)
PLUGIN:SaveDoorData()
return "@dSetClass", L(classData.name, client)
else
return "@invalidClass"
end
end
end
})
================================================
FILE: plugins/doors/sh_plugin.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Doors"
PLUGIN.author = "Chessnut"
PLUGIN.description = "A simple door system."
-- luacheck: globals DOOR_OWNER DOOR_TENANT DOOR_GUEST DOOR_NONE
DOOR_OWNER = 3
DOOR_TENANT = 2
DOOR_GUEST = 1
DOOR_NONE = 0
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")
ix.util.Include("sh_commands.lua")
do
local entityMeta = FindMetaTable("Entity")
function entityMeta:CheckDoorAccess(client, access)
if (!self:IsDoor()) then
return false
end
access = access or DOOR_GUEST
local parent = self.ixParent
if (IsValid(parent)) then
return parent:CheckDoorAccess(client, access)
end
if (hook.Run("CanPlayerAccessDoor", client, self, access)) then
return true
end
if (self.ixAccess and (self.ixAccess[client] or 0) >= access) then
return true
end
return false
end
if (SERVER) then
function entityMeta:RemoveDoorAccessData()
local receivers = {}
for k, _ in pairs(self.ixAccess or {}) do
receivers[#receivers + 1] = k
end
if (#receivers > 0) then
net.Start("ixDoorMenu")
net.WriteEntity(self)
net.WriteTable({})
net.Send(receivers)
end
self.ixAccess = {}
self:SetDTEntity(0, nil)
-- Remove door information on child doors
PLUGIN:CallOnDoorChildren(self, function(child)
child:SetDTEntity(0, nil)
end)
end
end
end
-- Configurations for door prices.
ix.config.Add("doorCost", 10, "The price to purchase a door.", nil, {
data = {min = 0, max = 500},
category = "dConfigName"
})
ix.config.Add("doorSellRatio", 0.5, "How much of the door price is returned when selling a door.", nil, {
data = {min = 0, max = 1.0, decimals = 1},
category = "dConfigName"
})
ix.config.Add("doorLockTime", 1, "How long it takes to (un)lock a door.", nil, {
data = {min = 0, max = 10.0, decimals = 1},
category = "dConfigName"
})
================================================
FILE: plugins/doors/sv_plugin.lua
================================================
util.AddNetworkString("ixDoorMenu")
util.AddNetworkString("ixDoorPermission")
-- Variables for door data.
local variables = {
-- Whether or not the door will be disabled.
"disabled",
-- The name of the door.
"name",
-- Price of the door.
"price",
-- If the door is ownable.
"ownable",
-- The faction that owns a door.
"faction",
-- The class that owns a door.
"class",
-- Whether or not the door will be hidden.
"visible"
}
function PLUGIN:CallOnDoorChildren(entity, callback)
local parent
if (entity.ixChildren) then
parent = entity
elseif (entity.ixParent) then
parent = entity.ixParent
end
if (IsValid(parent)) then
callback(parent)
for k, _ in pairs(parent.ixChildren) do
local child = ents.GetMapCreatedEntity(k)
if (IsValid(child)) then
callback(child)
end
end
end
end
function PLUGIN:CopyParentDoor(child)
local parent = child.ixParent
if (IsValid(parent)) then
for _, v in ipairs(variables) do
local value = parent:GetNetVar(v)
if (child:GetNetVar(v) != value) then
child:SetNetVar(v, value)
end
end
end
end
-- Called after the entities have loaded.
function PLUGIN:LoadData()
-- Restore the saved door information.
local data = self:GetData()
if (!data) then
return
end
-- Loop through all of the saved doors.
for k, v in pairs(data) do
-- Get the door entity from the saved ID.
local entity = ents.GetMapCreatedEntity(k)
-- Check it is a valid door in-case something went wrong.
if (IsValid(entity) and entity:IsDoor()) then
-- Loop through all of our door variables.
for k2, v2 in pairs(v) do
if (k2 == "children") then
entity.ixChildren = v2
for index, _ in pairs(v2) do
local door = ents.GetMapCreatedEntity(index)
if (IsValid(door)) then
door.ixParent = entity
end
end
elseif (k2 == "faction") then
for k3, v3 in pairs(ix.faction.teams) do
if (k3 == v2) then
entity.ixFactionID = k3
entity:SetNetVar("faction", v3.index)
break
end
end
else
entity:SetNetVar(k2, v2)
end
end
end
end
end
-- Called before the gamemode shuts down.
function PLUGIN:SaveDoorData()
-- Create an empty table to save information in.
local data = {}
local doors = {}
for _, v in ents.Iterator() do
if (v:IsDoor()) then
doors[v:MapCreationID()] = v
end
end
local doorData
-- Loop through doors with information.
for k, v in pairs(doors) do
-- Another empty table for actual information regarding the door.
doorData = {}
-- Save all of the needed variables to the doorData table.
for _, v2 in ipairs(variables) do
local value = v:GetNetVar(v2)
if (value) then
doorData[v2] = v:GetNetVar(v2)
end
end
if (v.ixChildren) then
doorData.children = v.ixChildren
end
if (v.ixClassID) then
doorData.class = v.ixClassID
end
if (v.ixFactionID) then
doorData.faction = v.ixFactionID
end
-- Add the door to the door information.
if (!table.IsEmpty(doorData)) then
data[k] = doorData
end
end
-- Save all of the door information.
self:SetData(data)
end
function PLUGIN:CanPlayerUseDoor(client, entity)
if (entity:GetNetVar("disabled")) then
return false
end
end
-- Whether or not a player a player has any abilities over the door, such as locking.
function PLUGIN:CanPlayerAccessDoor(client, door, access)
local faction = door:GetNetVar("faction")
-- If the door has a faction set which the client is a member of, allow access.
if (faction and client:Team() == faction) then
return true
end
local class = door:GetNetVar("class")
-- If the door has a faction set which the client is a member of, allow access.
local classData = ix.class.list[class]
local charClass = client:GetCharacter():GetClass()
local classData2 = ix.class.list[charClass]
if (class and classData and classData2) then
if (classData.team) then
if (classData.team != classData2.team) then
return false
end
else
if (charClass != class) then
return false
end
end
return true
end
end
function PLUGIN:PostPlayerLoadout(client)
client:Give("ix_keys")
end
function PLUGIN:ShowTeam(client)
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("faction") and !entity:GetNetVar("class")) then
if (entity:CheckDoorAccess(client, DOOR_TENANT)) then
local door = entity
if (IsValid(door.ixParent)) then
door = door.ixParent
end
net.Start("ixDoorMenu")
net.WriteEntity(door)
net.WriteTable(door.ixAccess)
net.WriteEntity(entity)
net.Send(client)
elseif (!IsValid(entity:GetDTEntity(0))) then
ix.command.Run(client, "doorbuy")
else
client:NotifyLocalized("notAllowed")
end
return true
end
end
function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar)
if (prevChar) then
local doors = prevChar:GetVar("doors") or {}
for _, v in ipairs(doors) do
if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then
v:RemoveDoorAccessData()
end
end
prevChar:SetVar("doors", nil)
end
end
function PLUGIN:PlayerDisconnected(client)
local character = client:GetCharacter()
if (character) then
local doors = character:GetVar("doors") or {}
for _, v in ipairs(doors) do
if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then
v:RemoveDoorAccessData()
end
end
character:SetVar("doors", nil)
end
end
net.Receive("ixDoorPermission", function(length, client)
local door = net.ReadEntity()
local target = net.ReadEntity()
local access = net.ReadUInt(4)
if (IsValid(target) and target:GetCharacter() and door.ixAccess and door:GetDTEntity(0) == client and target != client) then
access = math.Clamp(access or 0, DOOR_NONE, DOOR_TENANT)
if (access == door.ixAccess[target]) then
return
end
door.ixAccess[target] = access
local recipient = {}
for k, v in pairs(door.ixAccess) do
if (v > DOOR_GUEST) then
recipient[#recipient + 1] = k
end
end
if (#recipient > 0) then
net.Start("ixDoorPermission")
net.WriteEntity(door)
net.WriteEntity(target)
net.WriteUInt(access, 4)
net.Send(recipient)
end
end
end)
================================================
FILE: plugins/logging.lua
================================================
PLUGIN.name = "Logging"
PLUGIN.author = "Black Tea"
PLUGIN.description = "You can modfiy the logging text/lists on this plugin."
if (SERVER) then
local L = Format
ix.log.AddType("chat", function(client, ...)
local arg = {...}
return L("[%s] %s: %s", arg[1], client:Name(), arg[2])
end)
ix.log.AddType("command", function(client, ...)
local arg = {...}
if (arg[2] and #arg[2] > 0) then
return L("%s used command '%s %s'.", client:Name(), arg[1], arg[2])
else
return L("%s used command '%s'.", client:Name(), arg[1])
end
end)
ix.log.AddType("cfgSet", function(client, ...)
local arg = {...}
return L("%s set %s to '%s'.", client:Name(), arg[1], arg[2])
end, FLAG_DANGER)
ix.log.AddType("connect", function(client, ...)
return L("%s has connected.", client:SteamName())
end, FLAG_NORMAL)
ix.log.AddType("disconnect", function(client, ...)
if (client:IsTimingOut()) then
return L("%s (%s) has disconnected (timed out).", client:SteamName(), client:SteamID())
else
return L("%s (%s) has disconnected.", client:SteamName(), client:SteamID())
end
end, FLAG_NORMAL)
ix.log.AddType("charCreate", function(client, ...)
local arg = {...}
return L("%s created the character '%s'", client:SteamName(), arg[1])
end, FLAG_SERVER)
ix.log.AddType("charLoad", function(client, ...)
local arg = {...}
return L("%s loaded the character '%s'", client:SteamName(), arg[1])
end, FLAG_SERVER)
ix.log.AddType("charDelete", function(client, ...)
local arg = {...}
return L("%s (%s) deleted character '%s'", client:SteamName(), client:SteamID(), arg[1])
end, FLAG_SERVER)
ix.log.AddType("itemAction", function(client, ...)
local arg = {...}
local item = arg[2]
return L("%s ran '%s' on item '%s' (#%s)", client:Name(), arg[1], item:GetName(), item:GetID())
end, FLAG_NORMAL)
ix.log.AddType("itemDestroy", function(client, itemName, itemID)
local name = client:GetName() ~= "" and client:GetName() or client:GetClass()
return L("%s destroyed a '%s' #%d.", name, itemName, itemID)
end, FLAG_WARNING)
ix.log.AddType("shipmentTake", function(client, ...)
local arg = {...}
return L("%s took '%s' from the shipment", client:Name(), arg[1])
end, FLAG_WARNING)
ix.log.AddType("shipmentOrder", function(client, ...)
return L("%s ordered a shipment", client:Name())
end, FLAG_SUCCESS)
ix.log.AddType("buy", function(client, ...)
local arg = {...}
return L("%s purchased '%s' from the NPC", client:Name(), arg[1])
end, FLAG_SUCCESS)
ix.log.AddType("buydoor", function(client, ...)
return L("%s has purchased a door.", client:Name())
end, FLAG_SUCCESS)
ix.log.AddType("selldoor", function(client, ...)
return L("%s has sold a door.", client:Name())
end, FLAG_SUCCESS)
ix.log.AddType("playerHurt", function(client, ...)
local arg = {...}
return L("%s has taken %d damage from %s.", client:Name(), arg[1], arg[2])
end, FLAG_WARNING)
ix.log.AddType("playerDeath", function(client, ...)
local arg = {...}
return L("%s has killed %s%s.", arg[1], client:Name(), arg[2] and (" with " .. arg[2]) or "")
end, FLAG_DANGER)
ix.log.AddType("money", function(client, amount)
return L("%s has %s %s.", client:Name(), amount < 0 and "lost" or "gained", ix.currency.Get(math.abs(amount)))
end, FLAG_SUCCESS)
ix.log.AddType("inventoryAdd", function(client, characterName, itemName, itemID)
return L("%s has gained a '%s' #%d.", characterName, itemName, itemID)
end, FLAG_WARNING)
ix.log.AddType("inventoryRemove", function(client, characterName, itemName, itemID)
return L("%s has lost a '%s' #%d.", characterName, itemName, itemID)
end, FLAG_WARNING)
ix.log.AddType("storageMoneyTake", function(client, entity, amount, total)
local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName()
return string.format("%s has taken %d %s from '%s' #%d (%d %s left).",
client:GetName(), amount, ix.currency.plural, name,
entity:GetInventory():GetID(), total, ix.currency.plural)
end)
ix.log.AddType("storageMoneyGive", function(client, entity, amount, total)
local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName()
return string.format("%s has given %d %s to '%s' #%d (%d %s left).",
client:GetName(), amount, ix.currency.plural, name,
entity:GetInventory():GetID(), total, ix.currency.plural)
end)
ix.log.AddType("roll", function(client, value, max)
return string.format("%s rolled %d out of %d.", client:Name(), value, max)
end)
ix.log.AddType("pluginLoaded", function(client, uniqueID)
return string.format("%s has enabled the %s plugin for next restart.", client:GetName(), uniqueID)
end)
ix.log.AddType("pluginUnloaded", function(client, uniqueID)
return string.format("%s has disabled the %s plugin for next restart.", client:GetName(), uniqueID)
end)
function PLUGIN:PlayerInitialSpawn(client)
ix.log.Add(client, "connect")
end
function PLUGIN:PlayerDisconnected(client)
ix.log.Add(client, "disconnect")
end
function PLUGIN:OnCharacterCreated(client, character)
ix.log.Add(client, "charCreate", character:GetName())
end
function PLUGIN:CharacterLoaded(character)
local client = character:GetPlayer()
ix.log.Add(client, "charLoad", character:GetName())
end
function PLUGIN:PreCharacterDeleted(client, character)
ix.log.Add(client, "charDelete", character:GetName())
end
function PLUGIN:ShipmentItemTaken(client, itemClass, amount)
local itemTable = ix.item.list[itemClass]
ix.log.Add(client, "shipmentTake", itemTable:GetName())
end
function PLUGIN:CreateShipment(client, shipmentEntity)
ix.log.Add(client, "shipmentOrder")
end
function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell)
end
function PLUGIN:PlayerInteractItem(client, action, item)
if (isentity(item)) then
if (IsValid(item)) then
local itemID = item.ixItemID
item = ix.item.instances[itemID]
else
return
end
elseif (isnumber(item)) then
item = ix.item.instances[item]
end
if (!item) then
return
end
ix.log.Add(client, "itemAction", action, item)
end
function PLUGIN:InventoryItemAdded(oldInv, inventory, item)
if (!inventory.owner or (oldInv and oldInv.owner == inventory.owner)) then
return
end
local character = ix.char.loaded[inventory.owner]
ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), item:GetName(), item:GetID())
if (item.isBag) then
local bagInventory = item:GetInventory()
if (!bagInventory) then
return
end
for k, _ in bagInventory:Iter() do
ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), k:GetName(), k:GetID())
end
end
end
function PLUGIN:InventoryItemRemoved(inventory, item)
if (!inventory.owner) then
return
end
local character = ix.char.loaded[inventory.owner]
ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), item:GetName(), item:GetID())
if (item.isBag) then
for k, _ in item:GetInventory():Iter() do
ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), k:GetName(), k:GetID())
end
end
end
end
================================================
FILE: plugins/mapscene.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Map Scenes"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds areas of the map that are visible during character selection."
PLUGIN.scenes = PLUGIN.scenes or {}
local x3, y3 = 0, 0
local realOrigin = Vector(0, 0, 0)
local realAngles = Angle(0, 0, 0)
local view = {}
if (CLIENT) then
PLUGIN.ordered = PLUGIN.ordered or {}
function PLUGIN:CalcView(client, origin, angles, fov)
local scenes = self.scenes
if (IsValid(ix.gui.characterMenu) and !IsValid(ix.gui.menu) and !ix.gui.characterMenu:IsClosing() and
!table.IsEmpty(scenes)) then
local key = self.index
local value = scenes[self.index]
if (!self.index or !value) then
value, key = table.Random(scenes)
self.index = key
end
if (self.orderedIndex or value.origin or isvector(key)) then
local curTime = CurTime()
self.orderedIndex = self.orderedIndex or 1
local ordered = self.ordered[self.orderedIndex]
if (ordered) then
key = ordered[1]
value = ordered[2]
end
if (!self.startTime) then
self.startTime = curTime
self.finishTime = curTime + 30
end
local fraction = math.min(math.TimeFraction(self.startTime, self.finishTime, CurTime()), 1)
if (value) then
realOrigin = LerpVector(fraction, key, value[1])
realAngles = LerpAngle(fraction, value[2], value[3])
end
if (fraction >= 1) then
self.startTime = curTime
self.finishTime = curTime + 30
if (ordered) then
self.orderedIndex = self.orderedIndex + 1
if (self.orderedIndex > #self.ordered) then
self.orderedIndex = 1
end
else
local keys = {}
for k, _ in pairs(scenes) do
if (isvector(k)) then
keys[#keys + 1] = k
end
end
self.index = keys[ math.random( #keys ) ]
end
end
elseif (value) then
realOrigin = value[1]
realAngles = value[2]
end
local x, y = gui.MousePos()
local x2, y2 = surface.ScreenWidth() * 0.5, surface.ScreenHeight() * 0.5
local frameTime = FrameTime() * 0.5
y3 = Lerp(frameTime, y3, math.Clamp((y - y2) / y2, -1, 1) * -6)
x3 = Lerp(frameTime, x3, math.Clamp((x - x2) / x2, -1, 1) * 6)
view.origin = realOrigin + realAngles:Up()*y3 + realAngles:Right()*x3
view.angles = realAngles + Angle(y3 * -0.5, x3 * -0.5, 0)
return view
end
end
function PLUGIN:PreDrawViewModel(viewModel, client, weapon)
if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) then
return true
end
end
net.Receive("ixMapSceneAdd", function()
local data = net.ReadTable()
PLUGIN.scenes[#PLUGIN.scenes + 1] = data
end)
net.Receive("ixMapSceneRemove", function()
local index = net.ReadUInt(16)
PLUGIN.scenes[index] = nil
end)
net.Receive("ixMapSceneAddPair", function()
local data = net.ReadTable()
local origin = net.ReadVector()
PLUGIN.scenes[origin] = data
table.insert(PLUGIN.ordered, {origin, data})
end)
net.Receive("ixMapSceneRemovePair", function()
local key = net.ReadVector()
PLUGIN.scenes[key] = nil
for k, v in ipairs(PLUGIN.ordered) do
if (v[1] == key) then
table.remove(PLUGIN.ordered, k)
break
end
end
end)
net.Receive("ixMapSceneSync", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress map scene data!\n")
return
end
-- Set the list of texts to the ones provided by the server.
PLUGIN.scenes = util.JSONToTable(uncompressed)
for k, v in pairs(PLUGIN.scenes) do
if (v.origin or isvector(k)) then
table.insert(PLUGIN.ordered, {v.origin and v.origin or k, v})
end
end
end)
else
util.AddNetworkString("ixMapSceneSync")
util.AddNetworkString("ixMapSceneAdd")
util.AddNetworkString("ixMapSceneRemove")
util.AddNetworkString("ixMapSceneAddPair")
util.AddNetworkString("ixMapSceneRemovePair")
function PLUGIN:SaveScenes()
self:SetData(self.scenes)
end
function PLUGIN:LoadData()
self.scenes = self:GetData() or {}
end
function PLUGIN:PlayerInitialSpawn(client)
local json = util.TableToJSON(self.scenes)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixMapSceneSync")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
function PLUGIN:AddScene(position, angles, position2, angles2)
local data
if (position2) then
data = {origin=position, position2, angles, angles2}
self.scenes[#self.scenes + 1] = data
net.Start("ixMapSceneAddPair")
net.WriteTable(data)
net.WriteVector(position)
net.Broadcast()
else
data = {position, angles}
self.scenes[#self.scenes + 1] = data
net.Start("ixMapSceneAdd")
net.WriteTable(data)
net.Broadcast()
end
self:SaveScenes()
end
end
ix.command.Add("MapSceneAdd", {
description = "@cmdMapSceneAdd",
privilege = "Manage Map Scenes",
adminOnly = true,
arguments = bit.bor(ix.type.bool, ix.type.optional),
OnRun = function(self, client, bIsPair)
local position, angles = client:EyePos(), client:EyeAngles()
-- This scene is in a pair for moving scenes.
if (tobool(bIsPair) and !client.ixScnPair) then
client.ixScnPair = {position, angles}
return "@mapRepeat"
else
if (client.ixScnPair) then
PLUGIN:AddScene(client.ixScnPair[1], client.ixScnPair[2], position, angles)
client.ixScnPair = nil
else
PLUGIN:AddScene(position, angles)
end
return "@mapAdd"
end
end
})
ix.command.Add("MapSceneRemove", {
description = "@cmdMapSceneRemove",
privilege = "Manage Map Scenes",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
radius = radius or 280
local position = client:GetPos()
local i = 0
for k, v in pairs(PLUGIN.scenes) do
local delete = false
if (isvector(k)) then
if (k:Distance(position) <= radius or v[1]:Distance(position) <= radius) then
delete = true
end
elseif (v[1]:Distance(position) <= radius) then
delete = true
end
if (delete) then
if (isvector(k)) then
net.Start("ixMapSceneRemovePair")
net.WriteVector(k)
net.Broadcast()
else
net.Start("ixMapSceneRemove")
net.WriteString(k)
net.Broadcast()
end
PLUGIN.scenes[k] = nil
i = i + 1
end
end
if (i > 0) then
PLUGIN:SaveScenes()
end
return "@mapDel", i
end
})
================================================
FILE: plugins/observer.lua
================================================
PLUGIN.name = "Observer"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds on to the no-clip mode to prevent intrusion."
CAMI.RegisterPrivilege({
Name = "Helix - Observer",
MinAccess = "admin"
})
ix.option.Add("observerTeleportBack", ix.type.bool, true, {
bNetworked = true,
category = "observer",
hidden = function()
return !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Observer", nil)
end
})
if (CLIENT) then
ix.option.Add("observerESP", ix.type.bool, true, {
category = "observer",
hidden = function()
return !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Observer", nil)
end
})
local dimDistance = 1024
local aimLength = 128
local barHeight = 2
function PLUGIN:HUDPaint()
local client = LocalPlayer()
if (ix.option.Get("observerESP", true) and client:GetMoveType() == MOVETYPE_NOCLIP and
!client:InVehicle() and CAMI.PlayerHasAccess(client, "Helix - Observer", nil)) then
local scrW, scrH = ScrW(), ScrH()
for _, v in player.Iterator() do
if (v == client or !v:GetCharacter() or client:GetAimVector():Dot((v:GetPos() - client:GetPos()):GetNormal()) < 0.65) then
continue
end
local screenPosition = v:GetPos():ToScreen()
local aimPosition = (v:GetPos() + v:GetAimVector() * aimLength):ToScreen()
local marginX, marginY = scrH * .1, scrH * .1
local x, y = math.Clamp(screenPosition.x, marginX, scrW - marginX), math.Clamp(screenPosition.y, marginY, scrH - marginY)
local aimX, aimY = math.Clamp(aimPosition.x, marginX, scrW - marginX), math.Clamp(aimPosition.y, marginY, scrH - marginY)
local teamColor = team.GetColor(v:Team())
local distance = client:GetPos():Distance(v:GetPos())
local factor = 1 - math.Clamp(distance / dimDistance, 0, 1)
local size = math.max(10, 32 * factor)
local alpha = math.max(255 * factor, 80)
local aimAlpha = (1 - factor * 1.5) * 80
surface.SetDrawColor(teamColor.r, teamColor.g, teamColor.b, alpha)
surface.SetFont("ixGenericFont")
local text = v:Name()
local textWidth, textHeight = surface.GetTextSize(text)
local barWidth = math.Clamp((v:Health() / v:GetMaxHealth()) * textWidth, 0, textWidth)
surface.DrawRect(x - size / 2, y - size / 2, size, size)
-- we can assume that if we're using cheap blur, we'd want to save some fps here
if (!ix.option.Get("cheapBlur", false)) then
local data = {}
data.start = client:EyePos()
data.endpos = v:EyePos()
data.filter = {client, v}
if (util.TraceLine(data).Hit) then
aimAlpha = alpha
else
aimAlpha = (1 - factor * 4) * 80
end
end
if (aimPosition.visible) then
surface.SetDrawColor(teamColor.r * 1.2, teamColor.g * 1.2, teamColor.b * 1.2, aimAlpha)
surface.DrawLine(x, y, aimX, aimY)
surface.DrawLine(x, y + 1, aimX, aimY + 1)
end
surface.SetDrawColor(teamColor.r * 1.6, teamColor.g * 1.6, teamColor.b * 1.6, alpha)
surface.DrawRect(x - barWidth / 2, y - size - textHeight / 2, barWidth, barHeight)
ix.util.DrawText(text, x, y - size, ColorAlpha(teamColor, alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, nil, alpha)
end
end
end
function PLUGIN:ShouldPopulateEntityInfo(entity)
if (IsValid(entity)) then
if ((entity:IsPlayer() or IsValid(entity:GetNetVar("player"))) and entity:GetMoveType() == MOVETYPE_NOCLIP) then
return false
end
end
end
function PLUGIN:DrawPhysgunBeam(client, physgun, enabled, target, bone, hitPos)
if (client != LocalPlayer() and client:GetMoveType() == MOVETYPE_NOCLIP) then
return false
end
end
function PLUGIN:PrePlayerDraw(client)
if (client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) then
return true
end
end
else
ix.log.AddType("observerEnter", function(client, ...)
return string.format("%s entered observer.", client:Name())
end)
ix.log.AddType("observerExit", function(client, ...)
if (ix.option.Get(client, "observerTeleportBack", true)) then
return string.format("%s exited observer.", client:Name())
else
return string.format("%s exited observer at their location.", client:Name())
end
end)
function PLUGIN:CanPlayerEnterObserver(client)
if (CAMI.PlayerHasAccess(client, "Helix - Observer", nil)) then
return true
end
end
function PLUGIN:CanPlayerEnterVehicle(client, vehicle, role)
if (client:GetMoveType() == MOVETYPE_NOCLIP) then
return false
end
end
function PLUGIN:PlayerNoClip(client, state)
if (hook.Run("CanPlayerEnterObserver", client)) then
if (state) then
client.ixObsData = {client:GetPos(), client:EyeAngles()}
-- Hide them so they are not visible.
client:SetNoDraw(true)
client:SetNotSolid(true)
client:DrawWorldModel(false)
client:DrawShadow(false)
client:GodEnable()
client:SetNoTarget(true)
hook.Run("OnPlayerObserve", client, state)
else
if (client.ixObsData) then
-- Move they player back if they want.
if (ix.option.Get(client, "observerTeleportBack", true)) then
local position, angles = client.ixObsData[1], client.ixObsData[2]
-- Do it the next frame since the player can not be moved right now.
timer.Simple(0, function()
client:SetPos(position)
client:SetEyeAngles(angles)
client:SetVelocity(Vector(0, 0, 0))
end)
end
client.ixObsData = nil
end
-- Make the player visible again.
client:SetNoDraw(false)
client:SetNotSolid(false)
client:DrawWorldModel(true)
client:DrawShadow(true)
client:GodDisable()
client:SetNoTarget(false)
hook.Run("OnPlayerObserve", client, state)
end
return true
end
end
function PLUGIN:OnPlayerObserve(client, state)
if (state) then
ix.log.Add(client, "observerEnter")
else
ix.log.Add(client, "observerExit")
end
end
end
================================================
FILE: plugins/pac.lua
================================================
-- luacheck: globals pac pace
-- This Library is just for PAC3 Integration.
-- You must install PAC3 to make this library work.
PLUGIN.name = "PAC3 Integration"
PLUGIN.author = "Black Tea"
PLUGIN.description = "PAC3 integration for item parts."
if (!pace) then return end
ix.pac = ix.pac or {}
ix.pac.list = ix.pac.list or {}
CAMI.RegisterPrivilege({
Name = "Helix - Manage PAC",
MinAccess = "superadmin"
})
-- this stores pac3 part information to plugin's table'
function ix.pac.RegisterPart(id, outfit)
ix.pac.list[id] = outfit
end
-- Fixing the PAC3's default stuffs to fit on Helix.
if (CLIENT) then
-- Disable the "in editor" HUD element.
hook.Add("InitializedPlugins", "PAC3Fixer", function()
hook.Remove("HUDPaint", "pac_in_editor")
end)
-- Remove PAC3 LoadParts
function pace.LoadParts(name, clear, override_part) end
-- Prohibits players from deleting their own PAC3 outfit.
concommand.Add("pac_clear_parts", function()
RunConsoleCommand("pac_restart")
end)
-- you need the proper permission to open the editor
function PLUGIN:PrePACEditorOpen()
if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage PAC", nil)) then
return false
end
end
end
function PLUGIN:pac_CanWearParts(client)
if (!CAMI.PlayerHasAccess(client, "Helix - Manage PAC", nil)) then
return false
end
end
local meta = FindMetaTable("Player")
-- Get Player's PAC3 Parts.
function meta:GetParts()
if (!pac) then return end
return self:GetNetVar("parts", {})
end
if (SERVER) then
util.AddNetworkString("ixPartWear")
util.AddNetworkString("ixPartRemove")
util.AddNetworkString("ixPartReset")
function meta:AddPart(uniqueID, item)
if (!pac) then return end
local curParts = self:GetParts()
-- wear the parts.
net.Start("ixPartWear")
net.WriteEntity(self)
net.WriteString(uniqueID)
net.Broadcast()
curParts[uniqueID] = true
self:SetNetVar("parts", curParts)
end
function meta:RemovePart(uniqueID)
if (!pac) then return end
local curParts = self:GetParts()
-- remove the parts.
net.Start("ixPartRemove")
net.WriteEntity(self)
net.WriteString(uniqueID)
net.Broadcast()
curParts[uniqueID] = nil
self:SetNetVar("parts", curParts)
end
function meta:ResetParts()
if (!pac) then return end
net.Start("ixPartReset")
net.WriteEntity(self)
net.WriteTable(self:GetParts())
net.Broadcast()
self:SetNetVar("parts", {})
end
function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar)
-- Reset the characters parts.
local curParts = client:GetParts()
if (curParts) then
client:ResetParts()
end
-- After resetting all PAC3 outfits, wear all equipped PAC3 outfits.
if (curChar) then
local inv = curChar:GetInventory()
for k, _ in inv:Iter() do
if (k:GetData("equip") == true and k.pacData) then
client:AddPart(k.uniqueID, k)
end
end
end
end
function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon)
local oldItem = IsValid(oldWeapon) and oldWeapon.ixItem
local newItem = IsValid(newWeapon) and newWeapon.ixItem
if (oldItem and oldItem.isWeapon and oldItem:GetData("equip") and oldItem.pacData) then
oldItem:WearPAC(client)
end
if (newItem and newItem.isWeapon and newItem.pacData) then
newItem:RemovePAC(client)
end
end
-- Hides PAC parts when a player enters observer.
function PLUGIN:OnPlayerObserve(client, state)
local curParts = client:GetParts()
-- Remove all the parts
if (curParts) then
client:ResetParts()
end
-- If exiting of observer, re-add all parts.
if (!state) then
local character = client:GetCharacter()
local inventory = character:GetInventory()
for k, _ in inventory:Iter() do
if (k:GetData("equip") == true and k.pacData) then
client:AddPart(k.uniqueID, k)
end
end
end
end
else
local function AttachPart(client, uniqueID)
local itemTable = ix.item.list[uniqueID]
local pacData = ix.pac.list[uniqueID]
if (pacData) then
if (itemTable and itemTable.pacAdjust) then
pacData = table.Copy(pacData)
pacData = itemTable:pacAdjust(pacData, client)
end
if (isfunction(client.AttachPACPart)) then
client:AttachPACPart(pacData)
else
pac.SetupENT(client)
timer.Simple(0.1, function()
if (IsValid(client) and isfunction(client.AttachPACPart)) then
client:AttachPACPart(pacData)
end
end)
end
end
end
local function RemovePart(client, uniqueID)
local itemTable = ix.item.list[uniqueID]
local pacData = ix.pac.list[uniqueID]
if (pacData) then
if (itemTable and itemTable.pacAdjust) then
pacData = table.Copy(pacData)
pacData = itemTable:pacAdjust(pacData, client)
end
if (isfunction(client.RemovePACPart)) then
client:RemovePACPart(pacData)
else
pac.SetupENT(client)
end
end
end
hook.Add("Think", "ix_pacupdate", function()
if (!pac) then
hook.Remove("Think", "ix_pacupdate")
return
end
if (IsValid(pac.LocalPlayer)) then
for _, v in player.Iterator() do
local character = v:GetCharacter()
if (character) then
local parts = v:GetParts()
for k2, _ in pairs(parts) do
AttachPart(v, k2)
end
end
end
hook.Remove("Think", "ix_pacupdate")
end
end)
net.Receive("ixPartWear", function(length)
if (!pac) then return end
local wearer = net.ReadEntity()
local uid = net.ReadString()
if (!wearer.pac_owner) then
pac.SetupENT(wearer)
end
AttachPart(wearer, uid)
end)
net.Receive("ixPartRemove", function(length)
if (!pac) then return end
local wearer = net.ReadEntity()
local uid = net.ReadString()
if (!wearer.pac_owner) then
pac.SetupENT(wearer)
end
RemovePart(wearer, uid)
end)
net.Receive("ixPartReset", function(length)
if (!pac) then return end
local wearer = net.ReadEntity()
local uidList = net.ReadTable()
if (!wearer.pac_owner) then
pac.SetupENT(wearer)
end
for k, _ in pairs(uidList) do
RemovePart(wearer, k)
end
end)
function PLUGIN:DrawPlayerRagdoll(entity)
local ply = entity.objCache
if (IsValid(ply)) then
if (!entity.overridePAC3) then
if ply.pac_parts then
for _, part in pairs(ply.pac_parts) do
if part.last_owner and part.last_owner:IsValid() then
hook.Run("OnPAC3PartTransferred", part)
part:SetOwner(entity)
part.last_owner = entity
end
end
end
ply.pac_playerspawn = pac.RealTime -- used for events
entity.overridePAC3 = true
end
end
end
function PLUGIN:OnEntityCreated(entity)
local class = entity:GetClass()
-- For safe progress, I skip one frame.
timer.Simple(0.01, function()
if (class == "prop_ragdoll") then
if (entity:GetNetVar("player")) then
entity.RenderOverride = function()
entity.objCache = entity:GetNetVar("player")
entity:DrawModel()
hook.Run("DrawPlayerRagdoll", entity)
end
end
end
if (class:find("HL2MPRagdoll")) then
for _, v in player.Iterator() do
if (v:GetRagdollEntity() == entity) then
entity.objCache = v
end
end
entity.RenderOverride = function()
entity:DrawModel()
hook.Run("DrawPlayerRagdoll", entity)
end
end
end)
end
function PLUGIN:DrawCharacterOverview()
if (!pac) then
return
end
if (LocalPlayer().pac_outfits) then
pac.RenderOverride(LocalPlayer(), "opaque")
pac.RenderOverride(LocalPlayer(), "translucent", true)
end
end
function PLUGIN:DrawHelixModelView(panel, ent)
if (!pac) then
return
end
if (LocalPlayer():GetCharacter()) then
pac.RenderOverride(ent, "opaque")
pac.RenderOverride(ent, "translucent", true)
end
end
end
function PLUGIN:InitializedPlugins()
local items = ix.item.list
for _, v in pairs(items) do
if (v.pacData) then
ix.pac.list[v.uniqueID] = v.pacData
end
end
end
================================================
FILE: plugins/permakill.lua
================================================
PLUGIN.name = "Permakill"
PLUGIN.author = "Thadah Denyse"
PLUGIN.description = "Adds permanent death in the server options."
ix.config.Add("permakill", false, "Whether or not permakill is activated on the server.", nil, {
category = "Permakill"
})
ix.config.Add("permakillWorld", false, "Whether or not world and self damage produce permanent death.", nil, {
category = "Permakill"
})
function PLUGIN:PlayerDeath(client, inflictor, attacker)
local character = client:GetCharacter()
if (ix.config.Get("permakill") and character) then
if (hook.Run("ShouldPermakillCharacter", client, character, inflictor, attacker) == false) then
return
end
if (ix.config.Get("permakillWorld") and (client == attacker or inflictor:IsWorld())) then
return
end
character:SetData("permakilled", true)
end
end
function PLUGIN:PlayerSpawn(client)
local character = client:GetCharacter()
if (ix.config.Get("permakill") and character and character:GetData("permakilled")) then
character:Ban()
character:SetData("permakilled")
end
end
================================================
FILE: plugins/persistence.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Persistence"
PLUGIN.description = "Define entities to persist through restarts."
PLUGIN.author = "alexgrist"
PLUGIN.stored = PLUGIN.stored or {}
local function GetRealModel(entity)
return entity:GetClass() == "prop_effect" and entity.AttachedEntity:GetModel() or entity:GetModel()
end
properties.Add("persist", {
MenuLabel = "#makepersistent",
Order = 400,
MenuIcon = "icon16/link.png",
Filter = function(self, entity, client)
if (entity:IsPlayer() or entity:IsVehicle() or entity.bNoPersist) then return false end
if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end
return !entity:GetNetVar("Persistent", false)
end,
Action = function(self, entity)
self:MsgStart()
net.WriteEntity(entity)
self:MsgEnd()
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
PLUGIN.stored[#PLUGIN.stored + 1] = entity
entity:SetNetVar("Persistent", true)
ix.log.Add(client, "persist", GetRealModel(entity), true)
end
})
properties.Add("persist_end", {
MenuLabel = "#stoppersisting",
Order = 400,
MenuIcon = "icon16/link_break.png",
Filter = function(self, entity, client)
if (entity:IsPlayer()) then return false end
if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end
return entity:GetNetVar("Persistent", false)
end,
Action = function(self, entity)
self:MsgStart()
net.WriteEntity(entity)
self:MsgEnd()
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
for k, v in ipairs(PLUGIN.stored) do
if (v == entity) then
table.remove(PLUGIN.stored, k)
break
end
end
entity:SetNetVar("Persistent", false)
ix.log.Add(client, "persist", GetRealModel(entity), false)
end
})
function PLUGIN:PhysgunPickup(client, entity)
if (entity:GetNetVar("Persistent", false)) then
return false
end
end
if (SERVER) then
function PLUGIN:LoadData()
local entities = self:GetData() or {}
for _, v in ipairs(entities) do
local entity = ents.Create(v.Class)
if (IsValid(entity)) then
entity:SetPos(v.Pos)
entity:SetAngles(v.Angle)
entity:SetModel(v.Model)
entity:SetSkin(v.Skin)
entity:SetColor(v.Color)
entity:SetMaterial(v.Material)
entity:Spawn()
entity:Activate()
if (v.bNoCollision) then
entity:SetCollisionGroup(COLLISION_GROUP_WORLD)
end
if (istable(v.BodyGroups)) then
for k2, v2 in pairs(v.BodyGroups) do
entity:SetBodygroup(k2, v2)
end
end
if (istable(v.SubMaterial)) then
for k2, v2 in pairs(v.SubMaterial) do
if (!isnumber(k2) or !isstring(v2)) then
continue
end
entity:SetSubMaterial(k2 - 1, v2)
end
end
local physicsObject = entity:GetPhysicsObject()
if (IsValid(physicsObject)) then
physicsObject:EnableMotion(v.Movable)
end
self.stored[#self.stored + 1] = entity
entity:SetNetVar("Persistent", true)
end
end
end
function PLUGIN:SaveData()
local entities = {}
for _, v in ipairs(self.stored) do
if (IsValid(v)) then
local data = {}
data.Class = v.ClassOverride or v:GetClass()
data.Pos = v:GetPos()
data.Angle = v:GetAngles()
data.Model = GetRealModel(v)
data.Skin = v:GetSkin()
data.Color = v:GetColor()
data.Material = v:GetMaterial()
data.bNoCollision = v:GetCollisionGroup() == COLLISION_GROUP_WORLD
local materials = v:GetMaterials()
if (istable(materials)) then
data.SubMaterial = {}
for k2, _ in pairs(materials) do
if (v:GetSubMaterial(k2 - 1) != "") then
data.SubMaterial[k2] = v:GetSubMaterial(k2 - 1)
end
end
end
local bodyGroups = v:GetBodyGroups()
if (istable(bodyGroups)) then
data.BodyGroups = {}
for _, v2 in pairs(bodyGroups) do
if (v:GetBodygroup(v2.id) > 0) then
data.BodyGroups[v2.id] = v:GetBodygroup(v2.id)
end
end
end
local physicsObject = v:GetPhysicsObject()
if (IsValid(physicsObject)) then
data.Movable = physicsObject:IsMoveable()
end
entities[#entities + 1] = data
end
end
self:SetData(entities)
end
ix.log.AddType("persist", function(client, ...)
local arg = {...}
return string.format("%s has %s persistence for '%s'.", client:Name(), arg[2] and "enabled" or "disabled", arg[1])
end)
end
================================================
FILE: plugins/propprotect.lua
================================================
PLUGIN.name = "Basic Prop Protection"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds a simple prop protection system."
CAMI.RegisterPrivilege({
Name = "Helix - Bypass Prop Protection",
MinAccess = "admin"
})
local PROP_BLACKLIST = {
["models/props_combine/combinetrain02b.mdl"] = true,
["models/props_combine/combinetrain02a.mdl"] = true,
["models/props_combine/combinetrain01.mdl"] = true,
["models/cranes/crane_frame.mdl"] = true,
["models/props_junk/trashdumpster02.mdl"] = true,
["models/props_c17/oildrum001_explosive.mdl"] = true,
["models/props_canal/canal_bridge02.mdl"] = true,
["models/props_canal/canal_bridge01.mdl"] = true,
["models/props_canal/canal_bridge03a.mdl"] = true,
["models/props_canal/canal_bridge03b.mdl"] = true,
["models/props_wasteland/cargo_container01.mdl"] = true,
["models/props_wasteland/cargo_container01c.mdl"] = true,
["models/props_wasteland/cargo_container01b.mdl"] = true,
["models/props_combine/combine_mine01.mdl"] = true,
["models/props_junk/glassjug01.mdl"] = true,
["models/props_c17/paper01.mdl"] = true,
["models/props_junk/garbage_takeoutcarton001a.mdl"] = true,
["models/props_c17/trappropeller_engine.mdl"] = true,
["models/props/cs_office/microwave.mdl"] = true,
["models/items/item_item_crate.mdl"] = true,
["models/props_junk/gascan001a.mdl"] = true,
["models/props_c17/consolebox01a.mdl"] = true,
["models/props_buildings/building_002a.mdl"] = true,
["models/props_phx/mk-82.mdl"] = true,
["models/props_phx/cannonball.mdl"] = true,
["models/props_phx/ball.mdl"] = true,
["models/props_phx/amraam.mdl"] = true,
["models/props_phx/misc/flakshell_big.mdl"] = true,
["models/props_phx/ww2bomb.mdl"] = true,
["models/props_phx/torpedo.mdl"] = true,
["models/props/de_train/biohazardtank.mdl"] = true,
["models/props_buildings/project_building01.mdl"] = true,
["models/props_combine/prison01c.mdl"] = true,
["models/props/cs_militia/silo_01.mdl"] = true,
["models/props_phx/huge/evildisc_corp.mdl"] = true,
["models/props_phx/misc/potato_launcher_explosive.mdl"] = true,
["models/props_combine/combine_citadel001.mdl"] = true,
["models/props_phx/oildrum001_explosive.mdl"] = true,
["models/props_junk/wood_crate01_explosive.mdl"] = true,
["models/props_junk/propane_tank001a.mdl"] = true,
["models/props_explosive/explosive_butane_can.mdl"] = true,
["models/props_explosive/explosive_butane_can02.mdl"] = true
}
if (SERVER) then
ix.log.AddType("spawnProp", function(client, ...)
local arg = {...}
return string.format("%s has spawned '%s'.", client:Name(), arg[1])
end)
ix.log.AddType("spawnEntity", function(client, ...)
local arg = {...}
return string.format("%s has spawned a '%s'.", client:Name(), arg[1])
end)
function PLUGIN:PlayerSpawnObject(client, model, entity)
if ((client.ixNextSpawn or 0) < CurTime()) then
client.ixNextSpawn = CurTime() + 0.75
else
return false
end
if (!client:IsAdmin() and PROP_BLACKLIST[model:lower()]) then
return false
end
end
function PLUGIN:PhysgunPickup(client, entity)
local characterID = client:GetCharacter():GetID()
if (entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:OnPhysgunReload(weapon, client)
local characterID = client:GetCharacter():GetID()
local trace = client:GetEyeTrace()
if (IsValid(trace.Entity) and trace.Entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanProperty(client, property, entity)
local characterID = client:GetCharacter():GetID()
if (entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanTool(client, trace, tool)
local entity = trace.Entity
local characterID = client:GetCharacter():GetID()
if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:PlayerSpawnedProp(client, model, entity)
ix.log.Add(client, "spawnProp", model)
end
PLUGIN.PlayerSpawnedEffect = PLUGIN.PlayerSpawnedProp
PLUGIN.PlayerSpawnedRagdoll = PLUGIN.PlayerSpawnedProp
function PLUGIN:PlayerSpawnedNPC(client, entity)
ix.log.Add(client, "spawnEntity", entity)
end
PLUGIN.PlayerSpawnedSWEP = PLUGIN.PlayerSpawnedNPC
PLUGIN.PlayerSpawnedSENT = PLUGIN.PlayerSpawnedNPC
PLUGIN.PlayerSpawnedVehicle = PLUGIN.PlayerSpawnedNPC
else
function PLUGIN:PhysgunPickup(client, entity)
if (entity:GetNetVar("owner", 0) != client:GetCharacter():GetID()
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanProperty(client, property, entity)
local characterID = client:GetCharacter():GetID()
if (entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanTool(client, trace, tool)
local entity = trace.Entity
local characterID = client:GetCharacter():GetID()
if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
end
================================================
FILE: plugins/recognition.lua
================================================
PLUGIN.name = "Recognition"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds the ability to recognize people."
do
local character = ix.meta.character
if (SERVER) then
function character:Recognize(id)
if (!isnumber(id) and id.GetID) then
id = id:GetID()
end
local recognized = self:GetData("rgn", "")
if (recognized != "" and recognized:find(","..id..",")) then
return false
end
self:SetData("rgn", recognized..","..id..",")
return true
end
end
function character:DoesRecognize(id)
if (!isnumber(id) and id.GetID) then
id = id:GetID()
end
return hook.Run("IsCharacterRecognized", self, id)
end
function PLUGIN:IsCharacterRecognized(char, id)
if (char.id == id) then
return true
end
local other = ix.char.loaded[id]
if (other) then
local faction = ix.faction.indices[other:GetFaction()]
if (faction and faction.isGloballyRecognized) then
return true
end
end
local recognized = char:GetData("rgn", "")
if (recognized != "" and recognized:find(","..id..",")) then
return true
end
end
end
if (CLIENT) then
CHAT_RECOGNIZED = CHAT_RECOGNIZED or {}
CHAT_RECOGNIZED["ic"] = true
CHAT_RECOGNIZED["y"] = true
CHAT_RECOGNIZED["w"] = true
CHAT_RECOGNIZED["me"] = true
function PLUGIN:IsRecognizedChatType(chatType)
if (CHAT_RECOGNIZED[chatType]) then
return true
end
end
function PLUGIN:GetCharacterDescription(client)
if (client:GetCharacter() and client != LocalPlayer() and LocalPlayer():GetCharacter() and
!LocalPlayer():GetCharacter():DoesRecognize(client:GetCharacter()) and !hook.Run("IsPlayerRecognized", client)) then
return L"noRecog"
end
end
function PLUGIN:ShouldAllowScoreboardOverride(client)
if (ix.config.Get("scoreboardRecognition")) then
return true
end
end
function PLUGIN:GetCharacterName(client, chatType)
if (client != LocalPlayer()) then
local character = client:GetCharacter()
local ourCharacter = LocalPlayer():GetCharacter()
if (ourCharacter and character and !ourCharacter:DoesRecognize(character) and !hook.Run("IsPlayerRecognized", client)) then
if (chatType and hook.Run("IsRecognizedChatType", chatType)) then
local description = character:GetDescription()
if (#description > 40) then
description = description:utf8sub(1, 37).."..."
end
return "["..description.."]"
elseif (!chatType) then
return L"unknown"
end
end
end
end
local function Recognize(level)
net.Start("ixRecognize")
net.WriteUInt(level, 2)
net.SendToServer()
end
net.Receive("ixRecognizeMenu", function(length)
local menu = DermaMenu()
menu:AddOption(L"rgnLookingAt", function()
Recognize(0)
end)
menu:AddOption(L"rgnWhisper", function()
Recognize(1)
end)
menu:AddOption(L"rgnTalk", function()
Recognize(2)
end)
menu:AddOption(L"rgnYell", function()
Recognize(3)
end)
menu:Open()
menu:MakePopup()
menu:Center()
end)
net.Receive("ixRecognizeDone", function(length)
hook.Run("CharacterRecognized")
end)
function PLUGIN:CharacterRecognized(client, recogCharID)
surface.PlaySound("buttons/button17.wav")
end
else
util.AddNetworkString("ixRecognize")
util.AddNetworkString("ixRecognizeMenu")
util.AddNetworkString("ixRecognizeDone")
function PLUGIN:ShowSpare1(client)
if (client:GetCharacter()) then
net.Start("ixRecognizeMenu")
net.Send(client)
end
end
net.Receive("ixRecognize", function(length, client)
local level = net.ReadUInt(2)
if (isnumber(level)) then
local targets = {}
if (level < 1) then
local entity = client:GetEyeTraceNoCursor().Entity
if (IsValid(entity) and entity:IsPlayer() and entity:GetCharacter()
and ix.chat.classes.ic:CanHear(client, entity)) then
targets[1] = entity
end
else
local class = "w"
if (level == 2) then
class = "ic"
elseif (level == 3) then
class = "y"
end
class = ix.chat.classes[class]
for _, v in player.Iterator() do
if (client != v and v:GetCharacter() and class:CanHear(client, v)) then
targets[#targets + 1] = v
end
end
end
if (#targets > 0) then
local id = client:GetCharacter():GetID()
local i = 0
for _, v in ipairs(targets) do
if (v:GetCharacter():Recognize(id)) then
i = i + 1
end
end
if (i > 0) then
net.Start("ixRecognizeDone")
net.Send(client)
hook.Run("CharacterRecognized", client, id)
end
end
end
end)
end
================================================
FILE: plugins/saveitems.lua
================================================
PLUGIN.name = "Save Items"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Saves items that were dropped."
--[[
function PLUGIN:OnSavedItemLoaded(items)
for k, v in ipairs(items) do
-- do something
end
end
function PLUGIN:ShouldDeleteSavedItems()
return true
end
]]--
-- as title says.
function PLUGIN:LoadData()
local items = self:GetData()
if (items) then
local idRange = {}
local info = {}
for _, v in ipairs(items) do
idRange[#idRange + 1] = v[1]
info[v[1]] = {v[2], v[3], v[4]}
end
if (#idRange > 0) then
if (hook.Run("ShouldDeleteSavedItems") == true) then
-- don't spawn saved item and just delete them.
local query = mysql:Delete("ix_items")
query:WhereIn("item_id", idRange)
query:Execute()
print("Server Deleted Server Items (does not includes Logical Items)")
else
local query = mysql:Select("ix_items")
query:Select("item_id")
query:Select("unique_id")
query:Select("data")
query:WhereIn("item_id", idRange)
query:Callback(function(result)
if (istable(result)) then
local loadedItems = {}
local bagInventories = {}
for _, v in ipairs(result) do
local itemID = tonumber(v.item_id)
local data = util.JSONToTable(v.data or "[]")
local uniqueID = v.unique_id
local itemTable = ix.item.list[uniqueID]
if (itemTable and itemID) then
local item = ix.item.New(uniqueID, itemID)
item.data = data or {}
local itemInfo = info[itemID]
local position, angles, bMovable = itemInfo[1], itemInfo[2], true
if (isbool(itemInfo[3])) then
bMovable = itemInfo[3]
end
local itemEntity = item:Spawn(position, angles)
itemEntity.ixItemID = itemID
local physicsObject = itemEntity:GetPhysicsObject()
if (IsValid(physicsObject)) then
physicsObject:EnableMotion(bMovable)
end
item.invID = 0
loadedItems[#loadedItems + 1] = item
if (item.isBag) then
local invType = ix.item.inventoryTypes[uniqueID]
bagInventories[item:GetData("id")] = {invType.w, invType.h}
end
end
end
-- we need to manually restore bag inventories in the world since they don't have a current owner
-- that it can automatically restore along with the character when it's loaded
if (!table.IsEmpty(bagInventories)) then
ix.inventory.Restore(bagInventories)
end
hook.Run("OnSavedItemLoaded", loadedItems) -- when you have something in the dropped item.
end
end)
query:Execute()
end
end
end
end
function PLUGIN:SaveData()
local items = {}
for _, v in ipairs(ents.FindByClass("ix_item")) do
if (v.ixItemID and !v.bTemporary) then
local physicsObject = v:GetPhysicsObject()
local bMovable = nil
if (IsValid(physicsObject)) then
bMovable = physicsObject:IsMoveable()
end
items[#items + 1] = {
v.ixItemID, v:GetPos(), v:GetAngles(), bMovable
}
end
end
self:SetData(items)
end
================================================
FILE: plugins/spawns.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Spawns"
PLUGIN.description = "Spawn points for factions and classes."
PLUGIN.author = "Chessnut"
PLUGIN.spawns = PLUGIN.spawns or {}
function PLUGIN:PlayerLoadout(client)
local character = client:GetCharacter()
if (self.spawns and !table.IsEmpty(self.spawns) and character) then
local class = character:GetClass()
local points
local className = "default"
for k, v in ipairs(ix.faction.indices) do
if (k == client:Team()) then
points = self.spawns[v.uniqueID] or {}
break
end
end
if (points) then
for _, v in ipairs(ix.class.list) do
if (class == v.index) then
className = v.uniqueID
break
end
end
points = points[className] or points["default"]
if (points and !table.IsEmpty(points)) then
local position = points[ math.random( #points ) ]
client:SetPos(position)
end
end
end
end
function PLUGIN:LoadData()
self.spawns = self:GetData() or {}
end
function PLUGIN:SaveSpawns()
self:SetData(self.spawns)
end
ix.command.Add("SpawnAdd", {
description = "@cmdSpawnAdd",
privilege = "Manage Spawn Points",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.text, ix.type.optional)
},
OnRun = function(self, client, name, class)
local info = ix.faction.indices[name:lower()]
local info2
local faction
if (!info) then
for _, v in ipairs(ix.faction.indices) do
if (ix.util.StringMatches(v.uniqueID, name) or ix.util.StringMatches(L(v.name, client), name)) then
faction = v.uniqueID
info = v
break
end
end
end
if (info) then
if (class and class != "") then
local found = false
for _, v in ipairs(ix.class.list) do
if (v.faction == info.index and
(v.uniqueID:lower() == class:lower() or ix.util.StringMatches(L(v.name, client), class))) then
class = v.uniqueID
info2 = v
found = true
break
end
end
if (!found) then
return "@invalidClass"
end
else
class = "default"
end
PLUGIN.spawns[faction] = PLUGIN.spawns[faction] or {}
PLUGIN.spawns[faction][class] = PLUGIN.spawns[faction][class] or {}
table.insert(PLUGIN.spawns[faction][class], client:GetPos())
PLUGIN:SaveSpawns()
name = L(info.name, client)
if (info2) then
name = name .. " (" .. L(info2.name, client) .. ")"
end
return "@spawnAdded", name
else
return "@invalidFaction"
end
end
})
ix.command.Add("SpawnRemove", {
description = "@cmdSpawnRemove",
privilege = "Manage Spawn Points",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
radius = radius or 120
local position = client:GetPos()
local i = 0
for _, v in pairs(PLUGIN.spawns) do
for _, v2 in pairs(v) do
for k3, v3 in pairs(v2) do
if (v3:Distance(position) <= radius) then
v2[k3] = nil
i = i + 1
end
end
end
end
if (i > 0) then
PLUGIN:SaveSpawns()
end
return "@spawnDeleted", i
end
})
================================================
FILE: plugins/spawnsaver.lua
================================================
PLUGIN.name = "Spawn Saver"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Saves the position of a character."
-- Called right before the character has its information save.
function PLUGIN:CharacterPreSave(character)
-- Get the player from the character.
local client = character:GetPlayer()
-- Check to see if we can get the player's position.
if (IsValid(client)) then
local position, eyeAngles = client:GetPos(), client:EyeAngles()
-- Use pre-observer position to prevent spawning in the air.
if (client.ixObsData) then
position, eyeAngles = client.ixObsData[1], client.ixObsData[2]
end
-- Store the position in the character's data.
character:SetData("pos", {position, eyeAngles, game.GetMap()})
end
end
-- Called after the player's loadout has been set.
function PLUGIN:PlayerLoadedCharacter(client, character, lastChar)
timer.Simple(0, function()
if (IsValid(client)) then
-- Get the saved position from the character data.
local position = character:GetData("pos")
-- Check if the position was set.
if (position) then
if (position[3] and position[3]:lower() == game.GetMap():lower()) then
-- Restore the player to that position.
client:SetPos(position[1].x and position[1] or client:GetPos())
client:SetEyeAngles(position[2].p and position[2] or angle_zero)
end
-- Remove the position data since it is no longer needed.
character:SetData("pos", nil)
end
end
end)
end
================================================
FILE: plugins/stamina/attributes/sh_end.lua
================================================
ATTRIBUTE.name = "Endurance"
ATTRIBUTE.description = "Affects how long you can run for."
================================================
FILE: plugins/stamina/attributes/sh_stm.lua
================================================
ATTRIBUTE.name = "Stamina"
ATTRIBUTE.description = "Affects how fast you can run."
function ATTRIBUTE:OnSetup(client, value)
client:SetRunSpeed(ix.config.Get("runSpeed") + value)
end
================================================
FILE: plugins/stamina/sh_plugin.lua
================================================
PLUGIN.name = "Stamina"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds a stamina system to limit running."
-- luacheck: push ignore 631
ix.config.Add("staminaDrain", 1, "How much stamina to drain per tick (every quarter second). This is calculated before attribute reduction.", nil, {
data = {min = 0, max = 10, decimals = 2},
category = "characters"
})
ix.config.Add("staminaRegeneration", 1.75, "How much stamina to regain per tick (every quarter second).", nil, {
data = {min = 0, max = 10, decimals = 2},
category = "characters"
})
ix.config.Add("staminaCrouchRegeneration", 2, "How much stamina to regain per tick (every quarter second) while crouching.", nil, {
data = {min = 0, max = 10, decimals = 2},
category = "characters"
})
ix.config.Add("punchStamina", 10, "How much stamina punches use up.", nil, {
data = {min = 0, max = 100},
category = "characters"
})
-- luacheck: pop
local function CalcStaminaChange(client)
local character = client:GetCharacter()
if (!character or client:GetMoveType() == MOVETYPE_NOCLIP) then
return 0
end
local walkSpeed = ix.config.Get("walkSpeed")
local maxAttributes = ix.config.Get("maxAttributes", 100)
local offset
if (client:KeyDown(IN_SPEED) and client:GetVelocity():LengthSqr() >= (walkSpeed * walkSpeed) and client:OnGround()) then
-- characters could have attribute values greater than max if the config was changed
offset = -ix.config.Get("staminaDrain", 1) + math.min(character:GetAttribute("end", 0), maxAttributes) / 100
else
offset = client:Crouching() and ix.config.Get("staminaCrouchRegeneration", 2) or ix.config.Get("staminaRegeneration", 1.75)
end
offset = hook.Run("AdjustStaminaOffset", client, offset) or offset
if (CLIENT) then
return offset -- for the client we need to return the estimated stamina change
else
local current = client:GetLocalVar("stm", 0)
local value = math.Clamp(current + offset, 0, 100)
if (current != value) then
client:SetLocalVar("stm", value)
if (value == 0 and !client:GetNetVar("brth", false)) then
client:SetNetVar("brth", true)
character:UpdateAttrib("end", 0.1)
character:UpdateAttrib("stm", 0.01)
hook.Run("PlayerStaminaLost", client)
elseif (value >= 50 and client:GetNetVar("brth", false)) then
client:SetNetVar("brth", nil)
hook.Run("PlayerStaminaGained", client)
end
end
end
end
function PLUGIN:SetupMove(client, mv, cmd)
if (client:GetNetVar("brth", false)) then
mv:SetMaxClientSpeed(client:GetWalkSpeed())
end
end
if (SERVER) then
function PLUGIN:PostPlayerLoadout(client)
local uniqueID = "ixStam" .. client:SteamID()
timer.Create(uniqueID, 0.25, 0, function()
if (!IsValid(client)) then
timer.Remove(uniqueID)
return
end
CalcStaminaChange(client)
end)
end
function PLUGIN:CharacterPreSave(character)
local client = character:GetPlayer()
if (IsValid(client)) then
character:SetData("stamina", client:GetLocalVar("stm", 0))
end
end
function PLUGIN:PlayerLoadedCharacter(client, character)
timer.Simple(0.25, function()
client:SetLocalVar("stm", character:GetData("stamina", 100))
end)
end
local playerMeta = FindMetaTable("Player")
function playerMeta:RestoreStamina(amount)
local current = self:GetLocalVar("stm", 0)
local value = math.Clamp(current + amount, 0, 100)
self:SetLocalVar("stm", value)
end
function playerMeta:ConsumeStamina(amount)
local current = self:GetLocalVar("stm", 0)
local value = math.Clamp(current - amount, 0, 100)
self:SetLocalVar("stm", value)
end
else
local predictedStamina = 100
function PLUGIN:Think()
local offset = CalcStaminaChange(LocalPlayer())
-- the server check it every 0.25 sec, here we check it every [FrameTime()] seconds
offset = math.Remap(FrameTime(), 0, 0.25, 0, offset)
if (offset != 0) then
predictedStamina = math.Clamp(predictedStamina + offset, 0, 100)
end
end
function PLUGIN:OnLocalVarSet(key, var)
if (key != "stm") then return end
if (math.abs(predictedStamina - var) > 5) then
predictedStamina = var
end
end
ix.bar.Add(function()
return predictedStamina / 100
end, Color(200, 200, 40), nil, "stm")
end
================================================
FILE: plugins/strength/attributes/sh_str.lua
================================================
ATTRIBUTE.name = "Strength"
ATTRIBUTE.description = "A measure of how strong you are."
================================================
FILE: plugins/strength/sh_plugin.lua
================================================
PLUGIN.name = "Strength"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds a strength attribute."
if (SERVER) then
function PLUGIN:GetPlayerPunchDamage(client, damage, context)
if (client:GetCharacter()) then
-- Add to the total fist damage.
context.damage = context.damage + (client:GetCharacter():GetAttribute("str", 0) * ix.config.Get("strengthMultiplier"))
end
end
function PLUGIN:PlayerThrowPunch(client, trace)
if (client:GetCharacter() and IsValid(trace.Entity) and trace.Entity:IsPlayer()) then
client:GetCharacter():UpdateAttrib("str", 0.001)
end
end
end
-- Configuration for the plugin
ix.config.Add("strengthMultiplier", 0.3, "The strength multiplier scale", nil, {
data = {min = 0, max = 1.0, decimals = 1},
category = "Strength"
})
================================================
FILE: plugins/thirdperson.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Third Person"
PLUGIN.author = "Black Tea"
PLUGIN.description = "Enables third person camera usage."
ix.config.Add("thirdperson", false, "Allow Thirdperson in the server.", nil, {
category = "server"
})
if (CLIENT) then
local function isHidden()
return !ix.config.Get("thirdperson")
end
ix.option.Add("thirdpersonEnabled", ix.type.bool, false, {
category = "thirdperson",
hidden = isHidden,
OnChanged = function(oldValue, value)
hook.Run("ThirdPersonToggled", oldValue, value)
end
})
ix.option.Add("thirdpersonClassic", ix.type.bool, false, {
category = "thirdperson",
hidden = isHidden
})
ix.option.Add("thirdpersonVertical", ix.type.number, 10, {
category = "thirdperson", min = 0, max = 30,
hidden = isHidden
})
ix.option.Add("thirdpersonHorizontal", ix.type.number, 0, {
category = "thirdperson", min = -30, max = 30,
hidden = isHidden
})
ix.option.Add("thirdpersonDistance", ix.type.number, 50, {
category = "thirdperson", min = 0, max = 100,
hidden = isHidden
})
concommand.Add("ix_togglethirdperson", function()
local bEnabled = !ix.option.Get("thirdpersonEnabled", false)
ix.option.Set("thirdpersonEnabled", bEnabled)
end)
local function isAllowed()
return ix.config.Get("thirdperson")
end
local playerMeta = FindMetaTable("Player")
local traceMin = Vector(-10, -10, -10)
local traceMax = Vector(10, 10, 10)
function playerMeta:CanOverrideView()
local entity = Entity(self:GetLocalVar("ragdoll", 0))
if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing() and ix.gui.characterMenu:IsVisible()) then
return false
end
if (IsValid(ix.gui.menu) and ix.gui.menu:GetCharacterOverview()) then
return false
end
if (ix.option.Get("thirdpersonEnabled", false) and
!IsValid(self:GetVehicle()) and
isAllowed() and
IsValid(self) and
self:GetCharacter() and
!self:GetNetVar("actEnterAngle") and
!IsValid(entity) and
LocalPlayer():Alive()
) then
return true
end
end
local view, traceData, traceData2, aimOrigin, crouchFactor, ft, curAng, owner
local clmp = math.Clamp
crouchFactor = 0
function PLUGIN:CalcView(client, origin, angles, fov)
ft = FrameTime()
if (client:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then
local bNoclip = LocalPlayer():GetMoveType() == MOVETYPE_NOCLIP
if ((client:OnGround() and client:KeyDown(IN_DUCK)) or client:Crouching()) then
crouchFactor = Lerp(ft*5, crouchFactor, 1)
else
crouchFactor = Lerp(ft*5, crouchFactor, 0)
end
curAng = owner.camAng or angle_zero
view = {}
traceData = {}
traceData.start = client:GetPos() + client:GetViewOffset() +
curAng:Up() * ix.option.Get("thirdpersonVertical", 10) +
curAng:Right() * ix.option.Get("thirdpersonHorizontal", 0) -
client:GetViewOffsetDucked() * .5 * crouchFactor
traceData.endpos = traceData.start - curAng:Forward() * ix.option.Get("thirdpersonDistance", 50)
traceData.filter = client
traceData.ignoreworld = bNoclip
traceData.mins = traceMin
traceData.maxs = traceMax
view.origin = util.TraceHull(traceData).HitPos
aimOrigin = view.origin
view.angles = curAng + client:GetViewPunchAngles()
traceData2 = {}
traceData2.start = aimOrigin
traceData2.endpos = aimOrigin + curAng:Forward() * 65535
traceData2.filter = client
traceData2.ignoreworld = bNoclip
local bClassic = ix.option.Get("thirdpersonClassic", false)
if (bClassic or owner:IsWepRaised() or
(owner:KeyDown(bit.bor(IN_FORWARD, IN_BACK, IN_MOVELEFT, IN_MOVERIGHT)) and owner:GetVelocity():Length() >= 10)) then
client:SetEyeAngles((util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle())
else
local currentAngles = client:EyeAngles()
currentAngles.pitch = (util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle().pitch
client:SetEyeAngles(currentAngles)
end
return view
end
end
local diff, fm, sm
function PLUGIN:CreateMove(cmd)
owner = LocalPlayer()
if (owner:CanOverrideView() and owner:GetMoveType() != MOVETYPE_NOCLIP and
LocalPlayer():GetViewEntity() == LocalPlayer()) then
fm = cmd:GetForwardMove()
sm = cmd:GetSideMove()
diff = (owner:EyeAngles() - (owner.camAng or Angle(0, 0, 0)))[2] or 0
diff = diff / 90
cmd:SetForwardMove(fm + sm * diff)
cmd:SetSideMove(sm + fm * diff)
return false
end
end
function PLUGIN:InputMouseApply(cmd, x, y, ang)
owner = LocalPlayer()
if (!owner.camAng) then
owner.camAng = Angle(0, 0, 0)
end
owner.camAng.p = clmp(math.NormalizeAngle(owner.camAng.p + y / 50), -85, 85)
owner.camAng.y = math.NormalizeAngle(owner.camAng.y - x / 50)
if (owner:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then
return true
end
end
function PLUGIN:ShouldDrawLocalPlayer()
if (LocalPlayer():GetViewEntity() == LocalPlayer() and !IsValid(LocalPlayer():GetVehicle())) then
return LocalPlayer():CanOverrideView()
end
end
end
================================================
FILE: plugins/typing.lua
================================================
local PLUGIN = PLUGIN
PLUGIN.name = "Typing Indicator"
PLUGIN.description = "Shows an indicator when someone is typing."
PLUGIN.author = "`impulse"
PLUGIN.animationTime = 0.5
if (CLIENT) then
local standingOffset = Vector(0, 0, 72)
local crouchingOffset = Vector(0, 0, 38)
local boneOffset = Vector(0, 0, 10)
local textColor = Color(250, 250, 250)
local shadowColor = Color(66, 66, 66)
local currentClass
-- we can't rely on matching non-alphanumeric characters (i.e %W) due to patterns matching single bytes and not UTF-8 chars
local symbolPattern = "[~`!@#$%%%^&*()_%+%-={}%[%]|;:'\",%./<>?]"
function PLUGIN:LoadFonts(font, genericFont)
surface.CreateFont("ixTypingIndicator", {
font = genericFont,
size = 128,
extended = true,
weight = 1000
})
end
function PLUGIN:ChatTextChanged(text)
if (!IsValid(LocalPlayer())) then
return
end
local character = LocalPlayer():GetCharacter()
if (!character) then
return
end
if (text == "") then
currentClass = nil
net.Start("ixTypeClass")
net.WriteString("")
net.SendToServer()
return
end
local newClass = hook.Run("GetTypingIndicator", character, text)
if (newClass != currentClass) then
currentClass = newClass
net.Start("ixTypeClass")
net.WriteString(currentClass or "")
net.SendToServer()
end
end
function PLUGIN:FinishChat()
currentClass = nil
net.Start("ixTypeClass")
net.WriteString("")
net.SendToServer()
end
function PLUGIN:GetTypingIndicator(character, text)
local prefix = text:utf8sub(1, 1)
if (!prefix:find(symbolPattern) and text:utf8len() > 1) then
return "ic"
else
local chatType = ix.chat.Parse(nil, text)
if (chatType and chatType != "ic") then
return !ix.chat.classes[chatType].bNoIndicator and chatType or nil
end
-- some commands will have their own typing indicator, so we'll make sure we're actually typing out a command first
local start, _, commandName = text:find("/(%S+)%s")
if (start == 1) then
for uniqueID, command in pairs(ix.command.list) do
if (command.bNoIndicator) then
continue
end
if (commandName == uniqueID) then
return command.indicator and "@" .. command.indicator or "ooc"
end
end
end
end
end
function PLUGIN:GetTypingIndicatorPosition(client)
local head
for i = 1, client:GetBoneCount() do
local name = client:GetBoneName(i)
if (string.find(name:lower(), "head")) then
head = i
break
end
end
local position = head and client:GetBonePosition(head) or (client:Crouching() and crouchingOffset or standingOffset)
return position + boneOffset
end
function PLUGIN:PostDrawTranslucentRenderables()
local client = LocalPlayer()
local position = client:GetPos()
for _, v in player.Iterator() do
if (v == client) then
continue
end
local distance = v:GetPos():DistToSqr(position)
local moveType = v:GetMoveType()
if (!IsValid(v) or !v:Alive() or
(moveType != MOVETYPE_WALK and moveType != MOVETYPE_NONE) or
!v.ixChatClassText or
distance >= v.ixChatClassRange) then
continue
end
local text = v.ixChatClassText
local range = v.ixChatClassRange
local bAnimation = !ix.option.Get("disableAnimations", false)
local fraction
if (bAnimation) then
local bComplete = v.ixChatClassTween:update(FrameTime())
if (bComplete and !v.ixChatStarted) then
v.ixChatClassText = nil
v.ixChatClassRange = nil
continue
end
fraction = v.ixChatClassAnimation
else
fraction = 1
end
local angle = EyeAngles()
angle:RotateAroundAxis(angle:Forward(), 90)
angle:RotateAroundAxis(angle:Right(), 90)
cam.Start3D2D(self:GetTypingIndicatorPosition(v), Angle(0, angle.y, 90), 0.05)
surface.SetFont("ixTypingIndicator")
local _, textHeight = surface.GetTextSize(text)
local alpha = bAnimation and ((1 - math.min(distance, range) / range) * 255 * fraction) or 255
draw.SimpleTextOutlined(text, "ixTypingIndicator", 0,
-textHeight * 0.5 * fraction,
ColorAlpha(textColor, alpha),
TEXT_ALIGN_CENTER,
TEXT_ALIGN_CENTER, 4,
ColorAlpha(shadowColor, alpha)
)
cam.End3D2D()
end
end
net.Receive("ixTypeClass", function()
local client = net.ReadEntity()
if (!IsValid(client) or client == LocalPlayer()) then
return
end
local newClass = net.ReadString()
local chatClass = ix.chat.classes[newClass]
local text
local range
if (chatClass) then
text = L(chatClass.indicator or "chatTyping")
range = chatClass.range or math.pow(ix.config.Get("chatRange", 280), 2)
elseif (newClass and newClass:sub(1, 1) == "@") then
text = L(newClass:sub(2))
range = math.pow(ix.config.Get("chatRange", 280), 2)
end
if (ix.option.Get("disableAnimations", false)) then
client.ixChatClassText = text
client.ixChatClassRange = range
else
client.ixChatClassAnimation = tonumber(client.ixChatClassAnimation) or 0
if (text and !client.ixChatStarted) then
client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 1}, "outCubic")
client.ixChatClassText = text
client.ixChatClassRange = range
client.ixChatStarted = true
elseif (!text and client.ixChatStarted) then
client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 0}, "inCubic")
client.ixChatStarted = nil
end
end
end)
else
util.AddNetworkString("ixTypeClass")
function PLUGIN:PlayerSpawn(client)
net.Start("ixTypeClass")
net.WriteEntity(client)
net.WriteString("")
net.Broadcast()
end
net.Receive("ixTypeClass", function(length, client)
if ((client.ixNextTypeClass or 0) > RealTime()) then
return
end
local newClass = net.ReadString()
-- send message to players in pvs only since they're the only ones who can see the indicator
-- we'll broadcast if the type class is empty because they might move out of pvs before the ending net message is sent
net.Start("ixTypeClass")
net.WriteEntity(client)
net.WriteString(newClass)
if (newClass == "") then
net.Broadcast()
else
net.SendPVS(client:GetPos())
end
client.ixNextTypeClass = RealTime() + 0.2
end)
end
================================================
FILE: plugins/vendor/derma/cl_vendor.lua
================================================
local PANEL = {}
AccessorFunc(PANEL, "bReadOnly", "ReadOnly", FORCE_BOOL)
function PANEL:Init()
self:SetSize(ScrW() * 0.45, ScrH() * 0.65)
self:SetTitle("")
self:MakePopup()
self:Center()
local header = self:Add("DPanel")
header:SetTall(34)
header:Dock(TOP)
self.vendorName = header:Add("DLabel")
self.vendorName:Dock(LEFT)
self.vendorName:SetWide(self:GetWide() * 0.5 - 7)
self.vendorName:SetText("John Doe")
self.vendorName:SetTextInset(4, 0)
self.vendorName:SetTextColor(color_white)
self.vendorName:SetFont("ixMediumFont")
self.ourName = header:Add("DLabel")
self.ourName:Dock(RIGHT)
self.ourName:SetWide(self:GetWide() * 0.5 - 7)
self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")")
self.ourName:SetTextInset(0, 0)
self.ourName:SetTextColor(color_white)
self.ourName:SetFont("ixMediumFont")
local footer = self:Add("DPanel")
footer:SetTall(34)
footer:Dock(BOTTOM)
footer:SetPaintBackground(false)
self.vendorSell = footer:Add("DButton")
self.vendorSell:SetFont("ixMediumFont")
self.vendorSell:SetWide(self.vendorName:GetWide())
self.vendorSell:Dock(LEFT)
self.vendorSell:SetContentAlignment(5)
-- The text says purchase but the vendor is selling it to us.
self.vendorSell:SetText(L"purchase")
self.vendorSell:SetTextColor(color_white)
self.vendorSell.DoClick = function(this)
if (IsValid(self.activeSell)) then
net.Start("ixVendorTrade")
net.WriteString(self.activeSell.item)
net.WriteBool(false)
net.SendToServer()
end
end
self.vendorBuy = footer:Add("DButton")
self.vendorBuy:SetFont("ixMediumFont")
self.vendorBuy:SetWide(self.ourName:GetWide())
self.vendorBuy:Dock(RIGHT)
self.vendorBuy:SetContentAlignment(5)
self.vendorBuy:SetText(L"sell")
self.vendorBuy:SetTextColor(color_white)
self.vendorBuy.DoClick = function(this)
if (IsValid(self.activeBuy)) then
net.Start("ixVendorTrade")
net.WriteString(self.activeBuy.item)
net.WriteBool(true)
net.SendToServer()
end
end
self.selling = self:Add("DScrollPanel")
self.selling:SetWide(self:GetWide() * 0.5 - 7)
self.selling:Dock(LEFT)
self.selling:DockMargin(0, 4, 0, 4)
self.selling:SetPaintBackground(true)
self.sellingItems = self.selling:Add("DListLayout")
self.sellingItems:SetSize(self.selling:GetSize())
self.sellingItems:DockPadding(0, 0, 0, 4)
self.sellingItems:SetTall(ScrH())
self.buying = self:Add("DScrollPanel")
self.buying:SetWide(self:GetWide() * 0.5 - 7)
self.buying:Dock(RIGHT)
self.buying:DockMargin(0, 4, 0, 4)
self.buying:SetPaintBackground(true)
self.buyingItems = self.buying:Add("DListLayout")
self.buyingItems:SetSize(self.buying:GetSize())
self.buyingItems:DockPadding(0, 0, 0, 4)
self.sellingList = {}
self.buyingList = {}
end
function PANEL:addItem(uniqueID, listID)
local entity = self.entity
local items = entity.items
local data = items[uniqueID]
if ((!listID or listID == "selling") and !IsValid(self.sellingList[uniqueID])
and ix.item.list[uniqueID]) then
if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_BUYONLY) then
local item = self.sellingItems:Add("ixVendorItem")
item:Setup(uniqueID)
self.sellingList[uniqueID] = item
self.sellingItems:InvalidateLayout()
end
end
if ((!listID or listID == "buying") and !IsValid(self.buyingList[uniqueID])
and LocalPlayer():GetCharacter():GetInventory():HasItem(uniqueID)) then
if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_SELLONLY) then
local item = self.buyingItems:Add("ixVendorItem")
item:Setup(uniqueID)
item.isLocal = true
self.buyingList[uniqueID] = item
self.buyingItems:InvalidateLayout()
end
end
end
function PANEL:removeItem(uniqueID, listID)
if (!listID or listID == "selling") then
if (IsValid(self.sellingList[uniqueID])) then
self.sellingList[uniqueID]:Remove()
self.sellingItems:InvalidateLayout()
end
end
if (!listID or listID == "buying") then
if (IsValid(self.buyingList[uniqueID])) then
self.buyingList[uniqueID]:Remove()
self.buyingItems:InvalidateLayout()
end
end
end
function PANEL:Setup(entity)
self.entity = entity
self:SetTitle(entity:GetDisplayName())
self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..entity.money..")" or ""))
self.vendorBuy:SetEnabled(!self:GetReadOnly())
self.vendorSell:SetEnabled(!self:GetReadOnly())
for k, _ in SortedPairs(entity.items) do
self:addItem(k, "selling")
end
for _, v in SortedPairs(LocalPlayer():GetCharacter():GetInventory():GetItems()) do
self:addItem(v.uniqueID, "buying")
end
end
function PANEL:OnRemove()
net.Start("ixVendorClose")
net.SendToServer()
if (IsValid(ix.gui.vendorEditor)) then
ix.gui.vendorEditor:Remove()
end
end
function PANEL:Think()
local entity = self.entity
if (!IsValid(entity)) then
self:Remove()
return
end
if ((self.nextUpdate or 0) < CurTime()) then
self:SetTitle(self.entity:GetDisplayName())
self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..ix.currency.Get(entity.money)..")" or ""))
self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")")
self.nextUpdate = CurTime() + 0.25
end
end
function PANEL:OnItemSelected(panel)
local price = self.entity:GetPrice(panel.item, panel.isLocal)
if (panel.isLocal) then
self.vendorBuy:SetText(L"sell".." ("..ix.currency.Get(price)..")")
else
self.vendorSell:SetText(L"purchase".." ("..ix.currency.Get(price)..")")
end
end
vgui.Register("ixVendor", PANEL, "DFrame")
PANEL = {}
function PANEL:Init()
self:SetTall(36)
self:DockMargin(4, 4, 4, 0)
self.icon = self:Add("SpawnIcon")
self.icon:SetPos(2, 2)
self.icon:SetSize(32, 32)
self.icon:SetModel("models/error.mdl")
self.name = self:Add("DLabel")
self.name:Dock(FILL)
self.name:DockMargin(42, 0, 0, 0)
self.name:SetFont("ixChatFont")
self.name:SetTextColor(color_white)
self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200))
self.click = self:Add("DButton")
self.click:Dock(FILL)
self.click:SetText("")
self.click.Paint = function() end
self.click.DoClick = function(this)
if (self.isLocal) then
ix.gui.vendor.activeBuy = self
else
ix.gui.vendor.activeSell = self
end
ix.gui.vendor:OnItemSelected(self)
end
end
function PANEL:SetCallback(callback)
self.click.DoClick = function(this)
callback()
self.selected = true
end
end
function PANEL:Setup(uniqueID)
local item = ix.item.list[uniqueID]
if (item) then
self.item = uniqueID
self.icon:SetModel(item:GetModel(), item:GetSkin())
self.name:SetText(item:GetName())
self.itemName = item:GetName()
self.click:SetHelixTooltip(function(tooltip)
ix.hud.PopulateItemTooltip(tooltip, item)
local entity = ix.gui.vendor.entity
if (entity and entity.items[self.item] and entity.items[self.item][VENDOR_MAXSTOCK]) then
local info = entity.items[self.item]
local stock = tooltip:AddRowAfter("name", "stock")
stock:SetText(string.format("Stock: %d/%d", info[VENDOR_STOCK], info[VENDOR_MAXSTOCK]))
stock:SetBackgroundColor(derma.GetColor("Info", self))
stock:SizeToContents()
end
end)
end
end
function PANEL:Think()
if ((self.nextUpdate or 0) < CurTime()) then
local entity = ix.gui.vendor.entity
if (entity and self.isLocal) then
local count = LocalPlayer():GetCharacter():GetInventory():GetItemCount(self.item)
if (count == 0) then
self:Remove()
end
end
self.nextUpdate = CurTime() + 0.1
end
end
function PANEL:Paint(w, h)
if (ix.gui.vendor.activeBuy == self or ix.gui.vendor.activeSell == self) then
surface.SetDrawColor(ix.config.Get("color"))
else
surface.SetDrawColor(0, 0, 0, 100)
end
surface.DrawRect(0, 0, w, h)
end
vgui.Register("ixVendorItem", PANEL, "DPanel")
================================================
FILE: plugins/vendor/derma/cl_vendoreditor.lua
================================================
local PANEL = {}
function PANEL:Init()
local entity = ix.gui.vendor.entity
self:SetSize(320, 480)
self:MoveLeftOf(ix.gui.vendor, 8)
self:MakePopup()
self:CenterVertical()
self:SetTitle(L"vendorEditor")
self.lblTitle:SetTextColor(color_white)
self.name = self:Add("DTextEntry")
self.name:Dock(TOP)
self.name:SetText(entity:GetDisplayName())
self.name:SetPlaceholderText(L"name")
self.name.OnEnter = function(this)
if (entity:GetDisplayName() != this:GetText()) then
self:updateVendor("name", this:GetText())
end
end
self.description = self:Add("DTextEntry")
self.description:Dock(TOP)
self.description:DockMargin(0, 4, 0, 0)
self.description:SetText(entity:GetDescription())
self.description:SetPlaceholderText(L"description")
self.description.OnEnter = function(this)
if (entity:GetDescription() != this:GetText()) then
self:updateVendor("description", this:GetText())
end
end
self.model = self:Add("DTextEntry")
self.model:Dock(TOP)
self.model:DockMargin(0, 4, 0, 0)
self.model:SetText(entity:GetModel())
self.model:SetPlaceholderText(L"model")
self.model.OnEnter = function(this)
if (entity:GetModel():lower() != this:GetText():lower()) then
self:updateVendor("model", this:GetText():lower())
end
end
local useMoney = tonumber(entity.money) != nil
self.money = self:Add("DTextEntry")
self.money:Dock(TOP)
self.money:DockMargin(0, 4, 0, 0)
self.money:SetText(!useMoney and "∞" or entity.money)
self.money:SetPlaceholderText(L"money")
self.money:SetDisabled(!useMoney)
self.money:SetEnabled(useMoney)
self.money:SetNumeric(true)
self.money.OnEnter = function(this)
local value = tonumber(this:GetText()) or entity.money
if (value == entity.money) then
return
end
self:updateVendor("money", value)
end
self.bubble = self:Add("DCheckBoxLabel")
self.bubble:SetText(L"vendorNoBubble")
self.bubble:Dock(TOP)
self.bubble:DockMargin(0, 4, 0, 0)
self.bubble:SetValue(entity:GetNoBubble() and 1 or 0)
self.bubble.OnChange = function(this, value)
if (this.noSend) then
this.noSend = nil
else
self:updateVendor("bubble", value)
end
end
self.useMoney = self:Add("DCheckBoxLabel")
self.useMoney:SetText(L"vendorUseMoney")
self.useMoney:Dock(TOP)
self.useMoney:DockMargin(0, 4, 0, 0)
self.useMoney:SetChecked(useMoney)
self.useMoney.OnChange = function(this, value)
self:updateVendor("useMoney")
end
self.sellScale = self:Add("DNumSlider")
self.sellScale:Dock(TOP)
self.sellScale:DockMargin(0, 4, 0, 0)
self.sellScale:SetText(L"vendorSellScale")
self.sellScale.Label:SetTextColor(color_white)
self.sellScale.TextArea:SetTextColor(color_white)
self.sellScale:SetDecimals(1)
self.sellScale.noSend = true
self.sellScale:SetValue(entity.scale)
self.sellScale.OnValueChanged = function(this, value)
if (this.noSend) then
this.noSend = nil
else
timer.Create("ixVendorScale", 1, 1, function()
if (IsValid(self) and IsValid(self.sellScale)) then
value = self.sellScale:GetValue()
if (value != entity.scale) then
self:updateVendor("scale", value)
end
end
end)
end
end
self.faction = self:Add("DButton")
self.faction:SetText(L"vendorFaction")
self.faction:Dock(TOP)
self.faction:SetTextColor(color_white)
self.faction:DockMargin(0, 4, 0, 0)
self.faction.DoClick = function(this)
if (IsValid(ix.gui.editorFaction)) then
ix.gui.editorFaction:Remove()
end
ix.gui.editorFaction = vgui.Create("ixVendorFactionEditor")
ix.gui.editorFaction.updateVendor = self.updateVendor
ix.gui.editorFaction.entity = entity
ix.gui.editorFaction:Setup()
end
self.searchBar = self:Add("DTextEntry")
self.searchBar:Dock(TOP)
self.searchBar:DockMargin(0, 4, 0, 0)
self.searchBar:SetUpdateOnType(true)
self.searchBar:SetPlaceholderText("Search...")
self.searchBar.OnValueChange = function(this, value)
self:ReloadItemList(value)
end
local menu
self.items = self:Add("DListView")
self.items:Dock(FILL)
self.items:DockMargin(0, 4, 0, 0)
self.items:AddColumn(L"name").Header:SetTextColor(color_black)
self.items:AddColumn(L"category").Header:SetTextColor(color_black)
self.items:AddColumn(L"mode").Header:SetTextColor(color_black)
self.items:AddColumn(L"price").Header:SetTextColor(color_black)
self.items:AddColumn(L"stock").Header:SetTextColor(color_black)
self.items:SetMultiSelect(false)
self.items.OnRowRightClick = function(this, index, line)
if (IsValid(menu)) then
menu:Remove()
end
local uniqueID = line.item
menu = DermaMenu()
-- Modes of the item.
local mode, panel = menu:AddSubMenu(L"mode")
panel:SetImage("icon16/key.png")
-- Disable buying/selling of the item.
mode:AddOption(L"none", function()
self:updateVendor("mode", {uniqueID, nil})
end):SetImage("icon16/cog_error.png")
-- Allow the vendor to sell and buy this item.
mode:AddOption(L"vendorBoth", function()
self:updateVendor("mode", {uniqueID, VENDOR_SELLANDBUY})
end):SetImage("icon16/cog.png")
-- Only allow the vendor to buy this item from players.
mode:AddOption(L"vendorBuy", function()
self:updateVendor("mode", {uniqueID, VENDOR_BUYONLY})
end):SetImage("icon16/cog_delete.png")
-- Only allow the vendor to sell this item to players.
mode:AddOption(L"vendorSell", function()
self:updateVendor("mode", {uniqueID, VENDOR_SELLONLY})
end):SetImage("icon16/cog_add.png")
local itemTable = ix.item.list[uniqueID]
-- Set the price of the item.
menu:AddOption(L"price", function()
Derma_StringRequest(
itemTable.GetName and itemTable:GetName() or L(itemTable.name),
L"vendorPriceReq",
entity:GetPrice(uniqueID),
function(text)
text = tonumber(text)
if (text == itemTable.price) then
text = nil
end
self:updateVendor("price", {uniqueID, text})
end
)
end):SetImage("icon16/coins.png")
-- Set the stock of the item or disable it.
local stock, menuPanel = menu:AddSubMenu(L"stock")
menuPanel:SetImage("icon16/table.png")
-- Disable the use of stocks for this item.
stock:AddOption(L"disable", function()
self:updateVendor("stockDisable", uniqueID)
end):SetImage("icon16/table_delete.png")
-- Edit the maximum stock for this item.
stock:AddOption(L"edit", function()
local _, max = entity:GetStock(uniqueID)
Derma_StringRequest(
itemTable.GetName and itemTable:GetName() or L(itemTable.name),
L"vendorStockReq",
max or 1,
function(text)
self:updateVendor("stockMax", {uniqueID, text})
end
)
end):SetImage("icon16/table_edit.png")
-- Edit the current stock of this item.
stock:AddOption(L"vendorEditCurStock", function()
Derma_StringRequest(
itemTable.GetName and itemTable:GetName() or L(itemTable.name),
L"vendorStockCurReq",
entity:GetStock(uniqueID) or 0,
function(text)
self:updateVendor("stock", {uniqueID, text})
end
)
end):SetImage("icon16/table_edit.png")
menu:Open()
end
self:ReloadItemList()
end
function PANEL:ReloadItemList(filter)
local entity = ix.gui.vendor.entity
self.lines = {}
self.items:Clear()
for k, v in SortedPairs(ix.item.list) do
local itemName = v.GetName and v:GetName() or L(v.name)
if (filter and !itemName:lower():find(filter:lower(), 1, false)) then
continue
end
local mode = entity.items[k] and entity.items[k][VENDOR_MODE]
local current, max = entity:GetStock(k)
local panel = self.items:AddLine(
itemName,
v.category or L"none",
mode and L(VENDOR_TEXT[mode]) or L"none",
entity:GetPrice(k),
max and current.."/"..max or "-"
)
panel.item = k
self.lines[k] = panel
end
end
function PANEL:OnRemove()
if (IsValid(ix.gui.vendor)) then
ix.gui.vendor:Remove()
end
if (IsValid(ix.gui.editorFaction)) then
ix.gui.editorFaction:Remove()
end
end
function PANEL:updateVendor(key, value)
net.Start("ixVendorEdit")
net.WriteString(key)
net.WriteType(value)
net.SendToServer()
end
vgui.Register("ixVendorEditor", PANEL, "DFrame")
================================================
FILE: plugins/vendor/derma/cl_vendorfaction.lua
================================================
local PANEL = {}
function PANEL:Init()
self:SetSize(256, 280)
self:Center()
self:MakePopup()
self:SetTitle(L"vendorFaction")
self.scroll = self:Add("DScrollPanel")
self.scroll:Dock(FILL)
self.scroll:DockPadding(0, 0, 0, 4)
self.factions = {}
self.classes = {}
for k, v in ipairs(ix.faction.indices) do
local panel = self.scroll:Add("DPanel")
panel:Dock(TOP)
panel:DockPadding(4, 4, 4, 4)
panel:DockMargin(0, 0, 0, 4)
local faction = panel:Add("DCheckBoxLabel")
faction:Dock(TOP)
faction:SetText(L(v.name))
faction:DockMargin(0, 0, 0, 4)
faction.OnChange = function(this, state)
self:updateVendor("faction", v.uniqueID)
end
self.factions[v.uniqueID] = faction
for _, v2 in ipairs(ix.class.list) do
if (v2.faction == k) then
local class = panel:Add("DCheckBoxLabel")
class:Dock(TOP)
class:DockMargin(16, 0, 0, 4)
class:SetText(L(v2.name))
class.OnChange = function(this, state)
self:updateVendor("class", v2.uniqueID)
end
self.classes[v2.uniqueID] = class
panel:SetTall(panel:GetTall() + class:GetTall() + 4)
end
end
end
end
function PANEL:Setup()
for k, _ in pairs(self.entity.factions or {}) do
self.factions[k]:SetChecked(true)
end
for k, _ in pairs(self.entity.classes or {}) do
self.classes[k]:SetChecked(true)
end
end
vgui.Register("ixVendorFactionEditor", PANEL, "DFrame")
================================================
FILE: plugins/vendor/entities/entities/ix_vendor.lua
================================================
ENT.Type = "anim"
ENT.PrintName = "Vendor"
ENT.Category = "Helix"
ENT.Spawnable = true
ENT.AdminOnly = true
ENT.isVendor = true
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("Bool", 0, "NoBubble")
self:NetworkVar("String", 0, "DisplayName")
self:NetworkVar("String", 1, "Description")
end
function ENT:Initialize()
if (SERVER) then
self:SetModel("models/mossman.mdl")
self:SetUseType(SIMPLE_USE)
self:SetMoveType(MOVETYPE_NONE)
self:DrawShadow(true)
self:InitPhysObj()
self:AddCallback("OnAngleChange", function(entity)
local mins, maxs = entity:GetAxisAlignedBoundingBox()
entity:SetCollisionBounds(mins, maxs)
end)
self.items = {}
self.messages = {}
self.factions = {}
self.classes = {}
self:SetDisplayName("John Doe")
self:SetDescription("")
self.receivers = {}
end
timer.Simple(1, function()
if (IsValid(self)) then
self:SetAnim()
end
end)
end
function ENT:InitPhysObj()
local mins, maxs = self:GetAxisAlignedBoundingBox()
local bPhysObjCreated = self:PhysicsInitBox(mins, maxs)
if (bPhysObjCreated) then
local physObj = self:GetPhysicsObject()
physObj:EnableMotion(false)
physObj:Sleep()
end
end
function ENT:GetAxisAlignedBoundingBox()
local mins, maxs = self:GetModelBounds()
mins = Vector(mins.x, mins.y, 0)
mins, maxs = self:GetRotatedAABB(mins, maxs)
return mins, maxs
end
function ENT:CanAccess(client)
local bAccess = false
local uniqueID = ix.faction.indices[client:Team()].uniqueID
if (self.factions and !table.IsEmpty(self.factions)) then
if (self.factions[uniqueID]) then
bAccess = true
else
return false
end
end
if (bAccess and self.classes and !table.IsEmpty(self.classes)) then
local class = ix.class.list[client:GetCharacter():GetClass()]
local classID = class and class.uniqueID
if (classID and !self.classes[classID]) then
return false
end
end
return true
end
function ENT:GetStock(uniqueID)
if (self.items[uniqueID] and self.items[uniqueID][VENDOR_MAXSTOCK]) then
return self.items[uniqueID][VENDOR_STOCK] or 0, self.items[uniqueID][VENDOR_MAXSTOCK]
end
end
function ENT:GetPrice(uniqueID, selling)
local price = ix.item.list[uniqueID] and self.items[uniqueID] and
self.items[uniqueID][VENDOR_PRICE] or ix.item.list[uniqueID].price or 0
if (selling) then
price = math.floor(price * (self.scale or 0.5))
end
return price
end
function ENT:CanSellToPlayer(client, uniqueID)
local data = self.items[uniqueID]
if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then
return false
end
if (data[VENDOR_MODE] == VENDOR_BUYONLY) then
return false
end
if (!client:GetCharacter():HasMoney(self:GetPrice(uniqueID))) then
return false
end
if (data[VENDOR_STOCK] and data[VENDOR_STOCK] < 1) then
return false
end
return true
end
function ENT:CanBuyFromPlayer(client, uniqueID)
local data = self.items[uniqueID]
if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then
return false
end
if (data[VENDOR_MODE] != VENDOR_SELLONLY) then
return false
end
if (!self:HasMoney(data[VENDOR_PRICE] or ix.item.list[uniqueID].price or 0)) then
return false
end
return true
end
function ENT:HasMoney(amount)
-- Vendor not using money system so they can always afford it.
if (!self.money) then
return true
end
return self.money >= amount
end
function ENT:SetAnim()
for k, v in ipairs(self:GetSequenceList()) do
if (v:lower():find("idle") and v != "idlenoise") then
return self:ResetSequence(k)
end
end
if (self:GetSequenceCount() > 1) then
self:ResetSequence(4)
end
end
if (SERVER) then
local PLUGIN = PLUGIN
function ENT:SpawnFunction(client, trace)
local angles = (trace.HitPos - client:GetPos()):Angle()
angles.r = 0
angles.p = 0
angles.y = angles.y + 180
local entity = ents.Create("ix_vendor")
entity:SetPos(trace.HitPos)
entity:SetAngles(angles)
entity:Spawn()
PLUGIN:SaveData()
return entity
end
function ENT:Use(activator)
local character = activator:GetCharacter()
if (!self:CanAccess(activator) or hook.Run("CanPlayerUseVendor", activator, self) == false) then
if (self.messages[VENDOR_NOTRADE]) then
activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_NOTRADE])
else
activator:NotifyLocalized("vendorNoTrade")
end
return
end
self.receivers[#self.receivers + 1] = activator
if (self.messages[VENDOR_WELCOME]) then
activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_WELCOME])
end
local items = {}
-- Only send what is needed.
for k, v in pairs(self.items) do
if (!table.IsEmpty(v) and (CAMI.PlayerHasAccess(activator, "Helix - Manage Vendors", nil) or v[VENDOR_MODE])) then
items[k] = v
end
end
self.scale = self.scale or 0.5
activator.ixVendor = self
-- force sync to prevent outdated inventories while buying/selling
if (character) then
character:GetInventory():Sync(activator, true)
end
net.Start("ixVendorOpen")
net.WriteEntity(self)
net.WriteUInt(self.money or 0, 16)
net.WriteTable(items)
net.WriteFloat(self.scale or 0.5)
net.Send(activator)
ix.log.Add(activator, "vendorUse", self:GetDisplayName())
end
function ENT:SetMoney(value)
self.money = value
net.Start("ixVendorMoney")
net.WriteUInt(value and value or -1, 16)
net.Send(self.receivers)
end
function ENT:GiveMoney(value)
if (self.money) then
self:SetMoney(self:GetMoney() + value)
end
end
function ENT:TakeMoney(value)
if (self.money) then
self:GiveMoney(-value)
end
end
function ENT:SetStock(uniqueID, value)
if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then
return
end
self.items[uniqueID] = self.items[uniqueID] or {}
self.items[uniqueID][VENDOR_STOCK] = math.min(value, self.items[uniqueID][VENDOR_MAXSTOCK])
net.Start("ixVendorStock")
net.WriteString(uniqueID)
net.WriteUInt(value, 16)
net.Send(self.receivers)
end
function ENT:AddStock(uniqueID, value)
if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then
return
end
self:SetStock(uniqueID, self:GetStock(uniqueID) + (value or 1))
end
function ENT:TakeStock(uniqueID, value)
if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then
return
end
self:AddStock(uniqueID, -(value or 1))
end
else
function ENT:CreateBubble()
self.bubble = ClientsideModel("models/extras/info_speech.mdl", RENDERGROUP_OPAQUE)
self.bubble:SetPos(self:GetPos() + Vector(0, 0, 84))
self.bubble:SetModelScale(0.6, 0)
end
function ENT:Draw()
local bubble = self.bubble
if (IsValid(bubble)) then
local realTime = RealTime()
bubble:SetRenderOrigin(self:GetPos() + Vector(0, 0, 84 + math.sin(realTime * 3) * 0.05))
bubble:SetRenderAngles(Angle(0, realTime * 100, 0))
end
self:DrawModel()
end
function ENT:Think()
local noBubble = self:GetNoBubble()
if (IsValid(self.bubble) and noBubble) then
self.bubble:Remove()
elseif (!IsValid(self.bubble) and !noBubble) then
self:CreateBubble()
end
if ((self.nextAnimCheck or 0) < CurTime()) then
self:SetAnim()
self.nextAnimCheck = CurTime() + 60
end
self:SetNextClientThink(CurTime() + 0.25)
return true
end
function ENT:OnRemove()
if (IsValid(self.bubble)) then
self.bubble:Remove()
end
end
ENT.PopulateEntityInfo = true
function ENT:OnPopulateEntityInfo(container)
local name = container:AddRow("name")
name:SetImportant()
name:SetText(self:GetDisplayName())
name:SizeToContents()
local descriptionText = self:GetDescription()
if (descriptionText != "") then
local description = container:AddRow("description")
description:SetText(self:GetDescription())
description:SizeToContents()
end
end
end
function ENT:GetMoney()
return self.money
end
================================================
FILE: plugins/vendor/sh_plugin.lua
================================================
-- luacheck: globals VENDOR_BUY VENDOR_SELL VENDOR_BOTH VENDOR_WELCOME VENDOR_LEAVE VENDOR_NOTRADE VENDOR_PRICE
-- luacheck: globals VENDOR_STOCK VENDOR_MODE VENDOR_MAXSTOCK VENDOR_SELLANDBUY VENDOR_SELLONLY VENDOR_BUYONLY VENDOR_TEXT
local PLUGIN = PLUGIN
PLUGIN.name = "Vendors"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds NPC vendors that can sell things."
CAMI.RegisterPrivilege({
Name = "Helix - Manage Vendors",
MinAccess = "admin"
})
VENDOR_BUY = 1
VENDOR_SELL = 2
VENDOR_BOTH = 3
-- Keys for vendor messages.
VENDOR_WELCOME = 1
VENDOR_LEAVE = 2
VENDOR_NOTRADE = 3
-- Keys for item information.
VENDOR_PRICE = 1
VENDOR_STOCK = 2
VENDOR_MODE = 3
VENDOR_MAXSTOCK = 4
-- Sell and buy the item.
VENDOR_SELLANDBUY = 1
-- Only sell the item to the player.
VENDOR_SELLONLY = 2
-- Only buy the item from the player.
VENDOR_BUYONLY = 3
if (SERVER) then
util.AddNetworkString("ixVendorOpen")
util.AddNetworkString("ixVendorClose")
util.AddNetworkString("ixVendorTrade")
util.AddNetworkString("ixVendorEdit")
util.AddNetworkString("ixVendorEditFinish")
util.AddNetworkString("ixVendorEditor")
util.AddNetworkString("ixVendorMoney")
util.AddNetworkString("ixVendorStock")
util.AddNetworkString("ixVendorAddItem")
function PLUGIN:SaveData()
local data = {}
for _, entity in ipairs(ents.FindByClass("ix_vendor")) do
local bodygroups = {}
for _, v in ipairs(entity:GetBodyGroups() or {}) do
bodygroups[v.id] = entity:GetBodygroup(v.id)
end
data[#data + 1] = {
name = entity:GetDisplayName(),
description = entity:GetDescription(),
pos = entity:GetPos(),
angles = entity:GetAngles(),
model = entity:GetModel(),
skin = entity:GetSkin(),
bodygroups = bodygroups,
bubble = entity:GetNoBubble(),
items = entity.items,
factions = entity.factions,
classes = entity.classes,
money = entity.money,
scale = entity.scale
}
end
self:SetData(data)
end
function PLUGIN:LoadData()
for _, v in ipairs(self:GetData() or {}) do
local entity = ents.Create("ix_vendor")
entity:SetPos(v.pos)
entity:SetAngles(v.angles)
entity:Spawn()
entity:SetModel(v.model)
entity:SetSkin(v.skin or 0)
entity:InitPhysObj()
entity:SetNoBubble(v.bubble)
entity:SetDisplayName(v.name)
entity:SetDescription(v.description)
for id, bodygroup in pairs(v.bodygroups or {}) do
entity:SetBodygroup(id, bodygroup)
end
local items = {}
for uniqueID, data in pairs(v.items) do
items[tostring(uniqueID)] = data
end
entity.items = items
entity.factions = v.factions or {}
entity.classes = v.classes or {}
entity.money = v.money
entity.scale = v.scale or 0.5
end
end
function PLUGIN:CanVendorSellItem(client, vendor, itemID)
local tradeData = vendor.items[itemID]
local char = client:GetCharacter()
if (!tradeData or !char) then
return false
end
if (!char:HasMoney(tradeData[1] or 0)) then
return false
end
return true
end
ix.log.AddType("vendorUse", function(client, ...)
local arg = {...}
return string.format("%s used the '%s' vendor.", client:Name(), arg[1])
end)
ix.log.AddType("vendorBuy", function(client, ...)
local arg = {...}
return string.format("%s purchased a '%s' from the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3])
end)
ix.log.AddType("vendorSell", function(client, ...)
local arg = {...}
return string.format("%s sold a '%s' to the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3])
end)
net.Receive("ixVendorClose", function(length, client)
local entity = client.ixVendor
if (IsValid(entity)) then
for k, v in ipairs(entity.receivers) do
if (v == client) then
table.remove(entity.receivers, k)
break
end
end
client.ixVendor = nil
end
end)
local function UpdateEditReceivers(receivers, key, value)
net.Start("ixVendorEdit")
net.WriteString(key)
net.WriteType(value)
net.Send(receivers)
end
net.Receive("ixVendorEdit", function(length, client)
if (!CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil)) then
return
end
local entity = client.ixVendor
if (!IsValid(entity)) then
return
end
local key = net.ReadString()
local data = net.ReadType()
local feedback = true
if (key == "name") then
entity:SetDisplayName(data)
elseif (key == "description") then
entity:SetDescription(data)
elseif (key == "bubble") then
entity:SetNoBubble(data)
elseif (key == "mode") then
local uniqueID = data[1]
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_MODE] = data[2]
UpdateEditReceivers(entity.receivers, key, data)
elseif (key == "price") then
local uniqueID = data[1]
data[2] = tonumber(data[2])
if (data[2]) then
data[2] = math.Round(data[2])
end
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_PRICE] = data[2]
UpdateEditReceivers(entity.receivers, key, data)
data = uniqueID
elseif (key == "stockDisable") then
local uniqueID = data[1]
entity.items[data] = entity.items[uniqueID] or {}
entity.items[data][VENDOR_MAXSTOCK] = nil
UpdateEditReceivers(entity.receivers, key, data)
elseif (key == "stockMax") then
local uniqueID = data[1]
data[2] = math.max(math.Round(tonumber(data[2]) or 1), 1)
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2]
entity.items[uniqueID][VENDOR_STOCK] = math.Clamp(entity.items[uniqueID][VENDOR_STOCK] or data[2], 1, data[2])
data[3] = entity.items[uniqueID][VENDOR_STOCK]
UpdateEditReceivers(entity.receivers, key, data)
data = uniqueID
elseif (key == "stock") then
local uniqueID = data[1]
entity.items[uniqueID] = entity.items[uniqueID] or {}
if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then
data[2] = math.max(math.Round(tonumber(data[2]) or 0), 0)
entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2]
end
data[2] = math.Clamp(math.Round(tonumber(data[2]) or 0), 0, entity.items[uniqueID][VENDOR_MAXSTOCK])
entity.items[uniqueID][VENDOR_STOCK] = data[2]
UpdateEditReceivers(entity.receivers, key, data)
data = uniqueID
elseif (key == "faction") then
local faction = ix.faction.teams[data]
if (faction) then
entity.factions[data] = !entity.factions[data]
if (!entity.factions[data]) then
entity.factions[data] = nil
end
end
local uniqueID = data
data = {uniqueID, entity.factions[uniqueID]}
elseif (key == "class") then
local class
for _, v in ipairs(ix.class.list) do
if (v.uniqueID == data) then
class = v
break
end
end
if (class) then
entity.classes[data] = !entity.classes[data]
if (!entity.classes[data]) then
entity.classes[data] = nil
end
end
local uniqueID = data
data = {uniqueID, entity.classes[uniqueID]}
elseif (key == "model") then
entity:SetModel(data)
entity:InitPhysObj()
entity:SetAnim()
elseif (key == "useMoney") then
if (entity.money) then
entity:SetMoney()
else
entity:SetMoney(0)
end
elseif (key == "money") then
data = math.Round(math.abs(tonumber(data) or 0))
entity:SetMoney(data)
feedback = false
elseif (key == "scale") then
data = tonumber(data) or 0.5
entity.scale = data
UpdateEditReceivers(entity.receivers, key, data)
end
PLUGIN:SaveData()
if (feedback) then
local receivers = {}
for _, v in ipairs(entity.receivers) do
if (CAMI.PlayerHasAccess(v, "Helix - Manage Vendors", nil)) then
receivers[#receivers + 1] = v
end
end
net.Start("ixVendorEditFinish")
net.WriteString(key)
net.WriteType(data)
net.Send(receivers)
end
end)
net.Receive("ixVendorTrade", function(length, client)
if ((client.ixVendorTry or 0) < CurTime()) then
client.ixVendorTry = CurTime() + 0.33
else
return
end
local entity = client.ixVendor
if (!IsValid(entity) or client:GetPos():Distance(entity:GetPos()) > 192) then
return
end
if (!entity:CanAccess(client)) then
return
end
local uniqueID = net.ReadString()
local isSellingToVendor = net.ReadBool()
if (entity.items[uniqueID] and
hook.Run("CanPlayerTradeWithVendor", client, entity, uniqueID, isSellingToVendor) != false) then
local price = entity:GetPrice(uniqueID, isSellingToVendor)
if (isSellingToVendor) then
local found = false
local name
if (!entity:HasMoney(price)) then
return client:NotifyLocalized("vendorNoMoney")
end
local stock, max = entity:GetStock(uniqueID)
if (stock and stock >= max) then
return client:NotifyLocalized("vendorMaxStock")
end
local invOkay = true
for k, _ in client:GetCharacter():GetInventory():Iter() do
if (k.uniqueID == uniqueID and k:GetID() != 0 and ix.item.instances[k:GetID()] and k:GetData("equip", false) == false) then
invOkay = k:Remove()
found = true
name = L(k.name, client)
break
end
end
if (!found) then
return
end
if (!invOkay) then
client:GetCharacter():GetInventory():Sync(client, true)
return client:NotifyLocalized("tellAdmin", "trd!iid")
end
client:GetCharacter():GiveMoney(price, price == 0)
client:NotifyLocalized("businessSell", name, ix.currency.Get(price))
entity:TakeMoney(price)
entity:AddStock(uniqueID)
ix.log.Add(client, "vendorSell", name, entity:GetDisplayName(), ix.currency.Get(price))
else
local stock = entity:GetStock(uniqueID)
if (stock and stock < 1) then
return client:NotifyLocalized("vendorNoStock")
end
if (!client:GetCharacter():HasMoney(price)) then
return client:NotifyLocalized("canNotAfford")
end
if !entity:CanSellToPlayer(client, uniqueID) then
return false
end
local name = L(ix.item.list[uniqueID].name, client)
client:GetCharacter():TakeMoney(price, price == 0)
client:NotifyLocalized("businessPurchase", name, ix.currency.Get(price))
entity:GiveMoney(price)
if (!client:GetCharacter():GetInventory():Add(uniqueID)) then
ix.item.Spawn(uniqueID, client)
else
net.Start("ixVendorAddItem")
net.WriteString(uniqueID)
net.Send(client)
end
entity:TakeStock(uniqueID)
ix.log.Add(client, "vendorBuy", name, entity:GetDisplayName(), ix.currency.Get(price))
end
PLUGIN:SaveData()
hook.Run("CharacterVendorTraded", client, entity, uniqueID, isSellingToVendor)
else
client:NotifyLocalized("vendorNoTrade")
end
end)
else
VENDOR_TEXT = {}
VENDOR_TEXT[VENDOR_SELLANDBUY] = "vendorBoth"
VENDOR_TEXT[VENDOR_BUYONLY] = "vendorBuy"
VENDOR_TEXT[VENDOR_SELLONLY] = "vendorSell"
net.Receive("ixVendorOpen", function()
local entity = net.ReadEntity()
if (!IsValid(entity)) then
return
end
entity.money = net.ReadUInt(16)
entity.items = net.ReadTable()
entity.scale = net.ReadFloat()
ix.gui.vendor = vgui.Create("ixVendor")
ix.gui.vendor:SetReadOnly(false)
ix.gui.vendor:Setup(entity)
end)
net.Receive("ixVendorEditor", function()
local entity = net.ReadEntity()
if (!IsValid(entity) or !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Vendors", nil)) then
return
end
entity.money = net.ReadUInt(16)
entity.items = net.ReadTable()
entity.scale = net.ReadFloat()
entity.messages = net.ReadTable()
entity.factions = net.ReadTable()
entity.classes = net.ReadTable()
ix.gui.vendor = vgui.Create("ixVendor")
ix.gui.vendor:SetReadOnly(true)
ix.gui.vendor:Setup(entity)
ix.gui.vendorEditor = vgui.Create("ixVendorEditor")
end)
net.Receive("ixVendorEdit", function()
local panel = ix.gui.vendor
if (!IsValid(panel)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local key = net.ReadString()
local data = net.ReadType()
if (key == "mode") then
entity.items[data[1]] = entity.items[data[1]] or {}
entity.items[data[1]][VENDOR_MODE] = data[2]
if (!data[2]) then
panel:removeItem(data[1])
elseif (data[2] == VENDOR_SELLANDBUY) then
panel:addItem(data[1])
else
panel:addItem(data[1], data[2] == VENDOR_SELLONLY and "selling" or "buying")
panel:removeItem(data[1], data[2] == VENDOR_SELLONLY and "buying" or "selling")
end
elseif (key == "price") then
local uniqueID = data[1]
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_PRICE] = tonumber(data[2])
elseif (key == "stockDisable") then
if (entity.items[data]) then
entity.items[data][VENDOR_MAXSTOCK] = nil
end
elseif (key == "stockMax") then
local uniqueID = data[1]
local value = data[2]
local current = data[3]
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_MAXSTOCK] = value
entity.items[uniqueID][VENDOR_STOCK] = current
elseif (key == "stock") then
local uniqueID = data[1]
local value = data[2]
entity.items[uniqueID] = entity.items[uniqueID] or {}
if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then
entity.items[uniqueID][VENDOR_MAXSTOCK] = value
end
entity.items[uniqueID][VENDOR_STOCK] = value
elseif (key == "scale") then
entity.scale = data
end
end)
net.Receive("ixVendorEditFinish", function()
local panel = ix.gui.vendor
local editor = ix.gui.vendorEditor
if (!IsValid(panel) or !IsValid(editor)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local key = net.ReadString()
local data = net.ReadType()
if (key == "name") then
editor.name:SetText(data)
elseif (key == "description") then
editor.description:SetText(data)
elseif (key == "bubble") then
editor.bubble.noSend = true
editor.bubble:SetValue(data and 1 or 0)
elseif (key == "mode") then
if (data[2] == nil) then
editor.lines[data[1]]:SetValue(3, L"none")
else
editor.lines[data[1]]:SetValue(3, L(VENDOR_TEXT[data[2]]))
end
elseif (key == "price") then
editor.lines[data]:SetValue(4, entity:GetPrice(data))
elseif (key == "stockDisable") then
editor.lines[data]:SetValue(5, "-")
elseif (key == "stockMax" or key == "stock") then
local current, max = entity:GetStock(data)
editor.lines[data]:SetValue(5, current.."/"..max)
elseif (key == "faction") then
local uniqueID = data[1]
local state = data[2]
local editPanel = ix.gui.editorFaction
entity.factions[uniqueID] = state
if (IsValid(editPanel) and IsValid(editPanel.factions[uniqueID])) then
editPanel.factions[uniqueID]:SetChecked(state == true)
end
elseif (key == "class") then
local uniqueID = data[1]
local state = data[2]
local editPanel = ix.gui.editorFaction
entity.classes[uniqueID] = state
if (IsValid(editPanel) and IsValid(editPanel.classes[uniqueID])) then
editPanel.classes[uniqueID]:SetChecked(state == true)
end
elseif (key == "model") then
editor.model:SetText(entity:GetModel())
elseif (key == "scale") then
editor.sellScale.noSend = true
editor.sellScale:SetValue(data)
end
surface.PlaySound("buttons/button14.wav")
end)
net.Receive("ixVendorMoney", function()
local panel = ix.gui.vendor
if (!IsValid(panel)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local value = net.ReadUInt(16)
value = value != -1 and value or nil
entity.money = value
local editor = ix.gui.vendorEditor
if (IsValid(editor)) then
local useMoney = tonumber(value) != nil
editor.money:SetDisabled(!useMoney)
editor.money:SetEnabled(useMoney)
editor.money:SetText(useMoney and value or "∞")
end
end)
net.Receive("ixVendorStock", function()
local panel = ix.gui.vendor
if (!IsValid(panel)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local uniqueID = net.ReadString()
local amount = net.ReadUInt(16)
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_STOCK] = amount
local editor = ix.gui.vendorEditor
if (IsValid(editor)) then
local _, max = entity:GetStock(uniqueID)
editor.lines[uniqueID]:SetValue(4, amount .. "/" .. max)
end
end)
net.Receive("ixVendorAddItem", function()
local uniqueID = net.ReadString()
if (IsValid(ix.gui.vendor)) then
ix.gui.vendor:addItem(uniqueID, "buying")
end
end)
end
properties.Add("vendor_edit", {
MenuLabel = "Edit Vendor",
Order = 999,
MenuIcon = "icon16/user_edit.png",
Filter = function(self, entity, client)
if (!IsValid(entity)) then return false end
if (entity:GetClass() != "ix_vendor") then return false end
if (!gamemode.Call( "CanProperty", client, "vendor_edit", entity)) then return false end
return CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil)
end,
Action = function(self, entity)
self:MsgStart()
net.WriteEntity(entity)
self:MsgEnd()
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
entity.receivers[#entity.receivers + 1] = client
local itemsTable = {}
for k, v in pairs(entity.items) do
if (!table.IsEmpty(v)) then
itemsTable[k] = v
end
end
client.ixVendor = entity
net.Start("ixVendorEditor")
net.WriteEntity(entity)
net.WriteUInt(entity.money or 0, 16)
net.WriteTable(itemsTable)
net.WriteFloat(entity.scale or 0.5)
net.WriteTable(entity.messages)
net.WriteTable(entity.factions)
net.WriteTable(entity.classes)
net.Send(client)
end
})
================================================
FILE: plugins/wepselect.lua
================================================
PLUGIN.name = "Weapon Select"
PLUGIN.author = "Chessnut"
PLUGIN.description = "A replacement for the default weapon selection."
if (CLIENT) then
PLUGIN.index = PLUGIN.index or 1
PLUGIN.deltaIndex = PLUGIN.deltaIndex or PLUGIN.index
PLUGIN.infoAlpha = PLUGIN.infoAlpha or 0
PLUGIN.alpha = PLUGIN.alpha or 0
PLUGIN.alphaDelta = PLUGIN.alphaDelta or PLUGIN.alpha
PLUGIN.fadeTime = PLUGIN.fadeTime or 0
local matrixScale = Vector(1, 1, 0)
function PLUGIN:LoadFonts(font, genericFont)
surface.CreateFont("ixWeaponSelectFont", {
font = font,
size = ScreenScale(16),
extended = true,
weight = 1000
})
end
function PLUGIN:HUDShouldDraw(name)
if (name == "CHudWeaponSelection") then
return false
end
end
function PLUGIN:HUDPaint()
local frameTime = FrameTime()
self.alphaDelta = Lerp(frameTime * 10, self.alphaDelta, self.alpha)
local fraction = self.alphaDelta
if (fraction > 0.01) then
local x, y = ScrW() * 0.5, ScrH() * 0.5
local spacing = math.pi * 0.85
local radius = 240 * self.alphaDelta
local shiftX = ScrW() * .02
self.deltaIndex = Lerp(frameTime * 12, self.deltaIndex, self.index)
local weapons = LocalPlayer():GetWeapons()
local index = self.deltaIndex
if (!weapons[self.index]) then
self.index = #weapons
end
for i = 1, #weapons do
local theta = (i - index) * 0.1
local color = ColorAlpha(
i == self.index and ix.config.Get("color") or color_white,
(255 - math.abs(theta * 3) * 255) * fraction
)
local lastY = 0
if (self.markup and (i < self.index or i == 1)) then
if (self.index != 1) then
local _, h = self.markup:Size()
lastY = h * fraction
end
if (i == 1 or i == self.index - 1) then
self.infoAlpha = Lerp(frameTime * 3, self.infoAlpha, 255)
self.markup:Draw(x + 6 + shiftX, y + 30, 0, 0, self.infoAlpha * fraction)
end
end
surface.SetFont("ixWeaponSelectFont")
local weaponName = language.GetPhrase(weapons[i]:GetPrintName()):utf8upper()
local _, ty = surface.GetTextSize(weaponName)
local scale = 1 - math.abs(theta * 2)
local matrix = Matrix()
matrix:Translate(Vector(
shiftX + x + math.cos(theta * spacing + math.pi) * radius + radius,
y + lastY + math.sin(theta * spacing + math.pi) * radius - ty / 2 ,
1))
matrix:Scale(matrixScale * scale)
cam.PushModelMatrix(matrix)
ix.util.DrawText(weaponName, 2, ty / 2, color, 0, 1, "ixWeaponSelectFont")
cam.PopModelMatrix()
end
if (self.fadeTime < CurTime() and self.alpha > 0) then
self.alpha = 0
end
end
end
function PLUGIN:OnIndexChanged(weapon)
self.alpha = 1
self.fadeTime = CurTime() + 5
self.markup = nil
if (IsValid(weapon)) then
local instructions = weapon.Instructions
local text = ""
if (instructions != nil and instructions:find("%S")) then
local color = ix.config.Get("color")
text = text .. string.format(
"%s\n%s\n",
color.r, color.g, color.b, L("Instructions"), instructions
)
end
if (text != "") then
self.markup = markup.Parse(""..text, ScrW() * 0.3)
self.infoAlpha = 0
end
local source, pitch = hook.Run("WeaponCycleSound")
LocalPlayer():EmitSound(source or "common/talk.wav", 50, pitch or 180)
end
end
function PLUGIN:PlayerBindPress(client, bind, pressed)
bind = bind:lower()
if (!pressed or !bind:find("invprev") and !bind:find("invnext")
and !bind:find("slot") and !bind:find("attack")) then
return
end
local currentWeapon = client:GetActiveWeapon()
local bValid = IsValid(currentWeapon)
local bTool
if (client:InVehicle() or (bValid and currentWeapon:GetClass() == "weapon_physgun" and client:KeyDown(IN_ATTACK))) then
return
end
if (bValid and currentWeapon:GetClass() == "gmod_tool") then
local tool = client:GetTool()
bTool = tool and (tool.Scroll != nil)
end
local weapons = client:GetWeapons()
if (bind:find("invprev") and !bTool) then
local oldIndex = self.index
self.index = math.min(self.index + 1, #weapons)
if (self.alpha == 0 or oldIndex != self.index) then
self:OnIndexChanged(weapons[self.index])
end
return true
elseif (bind:find("invnext") and !bTool) then
local oldIndex = self.index
self.index = math.max(self.index - 1, 1)
if (self.alpha == 0 or oldIndex != self.index) then
self:OnIndexChanged(weapons[self.index])
end
return true
elseif (bind:find("slot")) then
self.index = math.Clamp(tonumber(bind:match("slot(%d)")) or 1, 1, #weapons)
self:OnIndexChanged(weapons[self.index])
return true
elseif (bind:find("attack") and self.alpha > 0) then
local weapon = weapons[self.index]
if (IsValid(weapon)) then
LocalPlayer():EmitSound(hook.Run("WeaponSelectSound", weapon) or "HL2Player.Use")
input.SelectWeapon(weapon)
self.alpha = 0
end
return true
end
end
function PLUGIN:Think()
local client = LocalPlayer()
if (!IsValid(client) or !client:Alive()) then
self.alpha = 0
end
end
function PLUGIN:ScoreboardShow()
self.alpha = 0
end
function PLUGIN:ShouldPopulateEntityInfo(entity)
if (self.alpha > 0) then
return false
end
end
end