This commit is contained in:
lifestorm
2024-08-04 23:12:27 +03:00
parent 8064ba84d8
commit 9c918c46e5
7081 changed files with 2173485 additions and 14 deletions

View File

@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2015 Brian Hang, Kyu Yeon Lee
Copyright (c) 2018-2021 Alexander Grist-Hucker, Igor Radovanovic
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.

72
gamemodes/helix/README.md Normal file
View File

@@ -0,0 +1,72 @@
This is a private branch from the [public helix](https://github.com/nebulousCloud/helix) repository.
In order to keep this repository up to date with any changes made to the public helix repository, you can pull changes from the public repo to your local PC, and then push those changes to our private repository.
Setup the public helix repository as another remote called 'public':
```
git remote add public https://github.com/NebulousCloud/helix.git
```
To pull in changes made on the public repository to your local PC:
```
git pull public master
```
Fix any merge conflicts after pulling changes from the public repository on your local PC. Once all has been merged (or there were no conflicts), you can:
```
git push
```
All commits will now have been nicely added to our private branch (including who made the commit, etc.).
<p align="center">
<img src="https://raw.githubusercontent.com/NebulousCloud/helix/master/docs/banner.gif" alt="Helix" />
</p>
<p align="center">
<a href="https://discord.gg/2AutUcF">
<img src="https://img.shields.io/discord/505957257125691423.svg" alt="Discord" />
</a>
<a href="https://github.com/NebulousCloud/helix/actions">
<img src="https://img.shields.io/github/workflow/status/NebulousCloud/helix/CI" alt="Build Status" />
</a>
</p>
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).

77
gamemodes/helix/config.ld Normal file
View File

@@ -0,0 +1,77 @@
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")

View File

@@ -0,0 +1 @@
GIF89a8h<01>

After

Width:  |  Height:  |  Size: 12 B

View File

@@ -0,0 +1,96 @@
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
.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;
}

View File

@@ -0,0 +1,522 @@
: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;
}

View File

@@ -0,0 +1,52 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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

View File

@@ -0,0 +1,58 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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

View File

@@ -0,0 +1,938 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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 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)
-- return false -- Disallow interacting with any entity.
-- 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
--- @realm shared
function CanPlayerUseBusiness(client, uniqueID)
end
--- @realm shared
function CanPlayerUseCharacter(client, character)
end
--- @realm server
function CanPlayerUseDoor(client, entity)
end
--- @realm server
function CanPlayerUseVendor(activator)
end
--- @realm client
function CanPlayerViewInventory()
end
--- @realm server
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 server
function CharacterLoaded(character)
end
--- @realm server
function CharacterPostSave(character)
end
--- @realm shared
function CharacterPreSave(character)
end
--- @realm shared
function CharacterRecognized()
end
--- @realm server
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
--- @realm server
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
--- @realm server
function CreateShipment(client, entity)
end
--- @realm server
function DatabaseConnected()
end
--- @realm server
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
--- @realm server
function GetPlayerPainSound(client)
end
--- @realm shared
function GetPlayerPunchDamage(client, damage, context)
end
--- @realm server
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
--- @realm server
function InventoryItemAdded(oldInv, inventory, item)
end
--- @realm server
function InventoryItemRemoved(inventory, item)
end
--- @realm shared
function IsCharacterRecognized(character, id)
end
--- @realm client
function IsPlayerRecognized(client)
end
--- @realm client
function IsRecognizedChatType(chatType)
end
--- @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
--- @realm shared
function OnCharacterDisconnect(client, character)
end
--- @realm server
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
--- @realm server
function OnPickupMoney(client, self)
end
--- @realm shared
function OnPlayerAreaChanged(client, oldID, newID)
end
--- @realm server
function OnPlayerObserve(client, state)
end
--- @realm server
function OnPlayerOptionSelected(client, callingClient, option)
end
--- @realm server
function OnPlayerPurchaseDoor(client, entity, bBuying, bCallOnDoorChild)
end
--- @realm server
function OnPlayerRestricted(client)
end
--- @realm server
function OnPlayerUnRestricted(client)
end
--- @realm server
function OnSavedItemLoaded(loadedItems)
end
--- @realm server
function OnWipeTables()
end
--- @realm shared
function PlayerEnterSequence(client, sequence, callback, time, bNoFreeze)
end
--- @realm server
function PlayerInteractEntity(client, entity, option, data)
end
--- @realm server
function PlayerInteractItem(client, action, item)
end
--- @realm server
function PlayerJoinedClass(client, class, oldClass)
end
--- @realm shared
function PlayerLeaveSequence(entity)
end
--- @realm server
function PlayerLoadedCharacter(client, character, currentChar)
end
--- @realm server
function PlayerLockedDoor(client, door, partner)
end
--- @realm server
function PlayerLockedVehicle(client, vehicle)
end
--- @realm server
function PlayerMessageSend(speaker, chatType, text, anonymous, receivers, rawText)
end
--- @realm shared
function PlayerModelChanged(client, model)
end
--- @realm server
function PlayerStaminaGained(client)
end
--- @realm server
function PlayerStaminaLost(client)
end
--- @realm shared
function PlayerThrowPunch(client, trace)
end
--- @realm server
function PlayerUnlockedDoor(client, door, partner)
end
--- @realm server
function PlayerUnlockedVehicle(client, door)
end
--- @realm server
function PlayerUse(client, entity)
end
--- @realm server
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
--- @realm server
function PostLoadData()
end
--- @realm server
function PostPlayerLoadout(client)
end
--- @realm server
function PostPlayerSay(client, chatType, message, anonymous)
end
--- @realm shared
function PostSetupActs()
end
--- @realm server
function PreCharacterDeleted(client, character)
end
--- @realm server
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
--- @realm server
function SaveData()
end
--- @realm client
function ScreenResolutionChanged(width, height)
end
--- @realm shared
function SetupActs()
end
--- @realm shared
function SetupAreaProperties()
end
--- @realm server
function ShipmentItemTaken(client, uniqueID, amount)
end
--- @realm client
function ShouldBarDraw(bar)
end
--- @realm server
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
--- @realm server
function ShouldPlayerDrowned(v)
end
--- @realm server
function ShouldRemoveRagdollOnDeath(client)
end
--- @realm server
function ShouldRestoreInventory(characterID, inventoryID, inventoryType)
end
--- @realm client
function ShouldShowPlayerOnScoreboard(client)
end
--- @realm server
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
--- @realm server
function VoiceDistanceChanged(newValue)
end
--- @realm client
function WeaponCycleSound()
end
--- @realm client
function WeaponSelectSound(weapon)
end

View File

@@ -0,0 +1,168 @@
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 diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,329 @@
# 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)

View File

@@ -0,0 +1,75 @@
# 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.
```

View File

@@ -0,0 +1,19 @@
<div class="landing">
<h1>Helix Documentation</h1>
</div>
<div class="wrapper">
<p style="text-align: center;">Welcome to the documentation for Helix - the better gamemode framework.</p>
<h2>Developers</h2>
<p>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 <a href="{* ldoc.url('manual/converting-from-clockwork') *}">conversion guide</a> in the manual.</p>
<h2>Server owners</h2>
<p>If you're looking to get your Helix server up and running as soon as possible, check out the <a href="{* ldoc.url('manual/getting-started') *}">Getting Started</a> guide in the manual.</p>
<h2>Community</h2>
<p>Questions? Want to show off your work? Maybe drop a new plugin release? Come join our community <a href="https://discord.gg/2AutUcF" target="_blank">Discord server</a>.</p>
<h2>Contributing</h2>
<p>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 <code>ix.storage</code> library's <a href="https://github.com/NebulousCloud/helix/blob/master/gamemode/core/libs/sh_storage.lua" target="_blank">source code</a> for a good example on how to write documentation. You'll need a basic understanding of <a href="https://guides.github.com/features/mastering-markdown/" target="_blank">Markdown</a>, since it's used extensively to generate the markup.</p>
<p>If you'd like to contribute code, you can visit the <a href="https://github.com/NebulousCloud/helix/" target="_blank">GitHub repository</a> and make a pull request.</p>
<h2>Learning</h2>
<p>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 <a href="https://wiki.facepunch.com/gmod/" target="_blank">Garry's Mod Wiki</a> is a good place to start.</p>
</div>

90
gamemodes/helix/docs/templates/ldoc.ltp vendored Normal file
View File

@@ -0,0 +1,90 @@
{%
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 "<span class=\"realm " .. (realm or "") .. "\"></span>";
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
%}
<html>
<head>
<title>{{pageTitle}}</title>
<meta property="og:type" content="website" />
<meta property="og:title" content="{{pageTitle}}" />
<meta property="og:site_name" content="Helix Documentation" />
{% if (mod) then %}
<meta property="og:description" content="{{moduleDescription(mod)}}" />
{% else %}
<meta property="og:description" content="Documentation and function reference for the Helix framework." />
{% end %}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Code+Pro" />
<link rel="stylesheet" href="{* ldoc.css *}" />
<link rel="stylesheet" href="{* ldoc.url('highlight.css') *}" />
</head>
<body>
<main>
{(docs/templates/sidebar.ltp)}
<article>
{% 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 %}
<div class="wrapper">
{* ldoc.body *}
</div>
{% elseif (module) then -- we're rendering libary contents %}
<div class="wrapper">
{(docs/templates/module.ltp)}
</div>
{% end %}
</article>
</main>
<script type="text/javascript" src="{* ldoc.url('app.js') *}"></script>
<script type="text/javascript" src="{* ldoc.url('highlight.min.js') *}"></script>
<script type="text/javascript">
var elements = document.querySelectorAll("pre code")
hljs.configure({
languages: ["lua"]
});
for (var i = 0; i < elements.length; i++)
{
hljs.highlightBlock(elements[i]);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,123 @@
<header class="module">
<h1>{{mod.name}}</h1>
<h2>{* ldoc.markup(mod.summary) *}</h2>
</header>
<p>{* ldoc.markup(mod.description) *}</p>
{% for kind, items in mod.kinds() do %}
<h1 class="title">{{kind}}</h1>
{% for item in items() do %}
<section class="method">
<header>
<a class="anchor" id="{{item.name}}">
<h1>{* ldoc.realm_icon(item.tags.realm[1]) *}</span>{{ldoc.display_name(item)}}</h1>
</a>
{% if (item.tags.internal) then %}
<div class="notice error">
<div class="title">Internal</div>
<p>This is an internal function! You are able to use it, but you risk unintended side effects if used incorrectly.</p>
</div>
{% end %}
{% if (item.module and item.module.type ~= "hooks") then %}
<a class="reference" href="{* ldoc.repo_reference(item) *}">View source &raquo;</a>
{% end %}
{% if (ldoc.descript(item):len() == 0) then %}
<div class="notice warning">
<div class="title">Incomplete</div>
<p>Documentation for this section is incomplete and needs expanding.</p>
</div>
{% else %}
<p>{* ldoc.markup(ldoc.descript(item)) *}</p>
{% end %}
</header>
{# function arguments #}
{% if (item.params and #item.params > 0) then %}
{% local subnames = mod.kinds:type_of(item).subnames %}
{% if (subnames) then %}
<h3>{{subnames}}</h3>
{% end %}
{% for argument in ldoc.modules.iter(item.params) do %}
{% local argument, sublist = item:subparam(argument) %}
<ul>
{% 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) %}
<li>
<span class="tag parameter">{{displayName}}</span>
{% if (type ~= "") then %}
<span class="tag">{* type *}</span>
{% end %}
{% if (default and default ~= true) then %}
<span class="tag default">default: {{default}}</span>
{% elseif (default) then %}
<span class="tag default">optional</span>
{% end %}
<p>{* ldoc.markup(item.params.map[argumentName]) *}</p>
</li>
{% end %}
</ul>
{% end %}
{% end %}
{# function returns #}
{% if ((not ldoc.no_return_or_parms) and item.retgroups) then %}
{% local groups = item.retgroups %}
<h3>Returns</h3>
<ul>
{% 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) %}
<li>
{% if (type ~= "") then %}
{* type *}
{% else -- we'll assume that it will return a variable type if none is set %}
<span class="tag type">any</span>
{% end %}
<p>{* ldoc.markup(returnValue.text) *}</p>
</li>
{% end %}
{% if (i ~= #groups) then %}
<div class="or"><span>OR</span></div>
{% end %}
{% end %}
</ul>
{% end %}
{% if (item.usage) then -- function usage %}
<h3>Example Usage</h3>
{% for usage in ldoc.modules.iter(item.usage) do %}
<pre><code>{* usage *}</code></pre>
{% end %}
{% end %}
{% if (item.see) then %}
<h3>See Also</h3>
<ul>
{% for see in ldoc.modules.iter(item.see) do %}
<li><a href="{* ldoc.href(see) *}">{{see.label}}</a></li>
{% end %}
</ul>
{% end %}
</section>
{% end %}
{% end %}

View File

@@ -0,0 +1,69 @@
{%
local function isKindExpandable(kind)
return kind ~= "Manual"
end
%}
<nav>
<header>
{% if (not ldoc.root) then %}
<h1><a href="{* ldoc.url('') *}">Helix Documentation</a></h1>
{% end %}
<input id="search" type="search" autocomplete="off" placeholder="Search..." />
</header>
<section>
{% for kind, mods, type in ldoc.kinds() do %}
{% if (ldoc.allowed_in_contents(type, mod)) then %}
<details class="category" open>
<summary>
<h2>{{kind}}</h2>
</summary>
<ul>
{% for currentMod in mods() do %}
{% local name = ldoc.display_name(currentMod) %}
<li>
{% if (isKindExpandable(kind)) then %}
<details {{currentMod.name == (mod or {}).name and "open" or ""}}>
<summary><a href="{* ldoc.ref_to_module(currentMod) *}">{{name}}</a></summary>
<ul>
{% else %}
<a href="{* ldoc.ref_to_module(currentMod) *}">{{name}}</a>
{% end %}
{% if (isKindExpandable(kind)) then
currentMod.items:sort(function(a, b)
return a.name < b.name
end)
end %}
{% for k, v in pairs(currentMod.items) do %}
{% if (v.kind == "functions") then %}
<li>
{* ldoc.realm_icon(v.tags.realm[1]) *}
<a href="{* ldoc.ref_to_module(currentMod) *}#{{v.name}}">
{% if (ldoc.is_kind_classmethod(currentMod.kind)) then
echo((v.name:gsub(".+:", "")))
else
echo((v.name:gsub(currentMod.name .. ".", "")))
end %}
</a>
</li>
{% end %}
{% end %}
{% if (isKindExpandable(kind)) then %}
</ul>
</details>
{% end %}
</li>
{% end %}
</ul>
</details>
{% end %}
{% end %}
</section>
</nav>

View File

@@ -0,0 +1,345 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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
function ENT:GetProxyColors()
local itemTable = self.GetItemTable and self:GetItemTable()
return itemTable and itemTable.proxy or false
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:AddEFlags(EFL_FORCE_CHECK_TRANSMIT)
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())
self:SetBodyGroups(itemTable:GetModelBodygroups())
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()
return
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

View File

@@ -0,0 +1,74 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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

View File

@@ -0,0 +1,147 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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

View File

@@ -0,0 +1,649 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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:PreDrawViewModel(viewModel, weapon, client)
local hands = player_manager.TranslatePlayerHands(player_manager.TranslateToPlayerModelName(client:GetModel()))
if client:GetModel() == "models/willardnetworks/vortigaunt.mdl" then
self.ViewModelFOV = 60
end
if (hands and hands.model) then
viewModel:SetModel(hands.model)
if hands.skin and isnumber(hands.skin) then
viewModel:SetSkin(hands.skin)
end
viewModel:SetBodyGroups(hands.body)
end
end
function SWEP:DoDrawCrosshair(x, y)
surface.SetDrawColor(255, 255, 255, 66)
surface.DrawRect(x - 2, y - 2, 4, 4)
end
-- Adjust these variables to move the viewmodel's position
SWEP.IronSightsPos = Vector(0, 0, 0)
SWEP.IronSightsAng = Vector(0, 0, 0)
function SWEP:GetViewModelPosition(EyePos, EyeAng)
if self.Owner:GetModel() == "models/willardnetworks/vortigaunt.mdl" then
self.IronSightsPos = Vector(0, -3, -5)
local Mul = 1.0
local Offset = self.IronSightsPos
if (self.IronSightsAng) then
EyeAng = EyeAng * 1
EyeAng:RotateAroundAxis(EyeAng:Right(), self.IronSightsAng.x * Mul)
EyeAng:RotateAroundAxis(EyeAng:Up(), self.IronSightsAng.y * Mul)
EyeAng:RotateAroundAxis(EyeAng:Forward(), self.IronSightsAng.z * Mul)
end
local Right = EyeAng:Right()
local Up = EyeAng:Up()
local Forward = EyeAng:Forward()
EyePos = EyePos + Offset.x * Right * Mul
EyePos = EyePos + Offset.y * Forward * Mul
EyePos = EyePos + Offset.z * Up * Mul
return EyePos, EyeAng
end
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
local pos = physics:GetPos()
local clientPos = client:GetPos()
if (clientPos.z > pos.z and math.Distance(clientPos.x, clientPos.y, pos.x, pos.y) <= 50) 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)
if !entity or entity and !IsValid(entity) then return false end
local physics = entity.GetPhysicsObject and entity:GetPhysicsObject()
local client = self:GetOwner()
local clientPos = client:GetPos()
local pos = physics and IsValid(physics) and physics:GetPos()
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", client, entity) and
!(clientPos.z > entity:GetPos().z and math.Distance(clientPos.x, clientPos.y, pos.x, pos.y) < 50)
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))
if ix.plugin.Get("vortigaunts") then
if (ix.config.Get("pushOnly")) and !self:GetOwner():IsVortigaunt() then
local data = {}
data.start = self.Owner:GetShootPos()
data.endpos = data.start + self.Owner:GetAimVector() * 84
data.filter = {self, self.Owner}
local trace = util.TraceLine(data)
local entity = trace.Entity
if (entity:IsPlayer() and ix.config.Get("allowPush", true)) then
self:PushEntity(entity)
end
return
end
else
if (ix.config.Get("pushOnly")) then
local data = {}
data.start = self.Owner:GetShootPos()
data.endpos = data.start + self.Owner:GetAimVector() * 84
data.filter = {self, self.Owner}
local trace = util.TraceLine(data)
local entity = trace.Entity
if (entity:IsPlayer() and ix.config.Get("allowPush", true)) then
self:PushEntity(entity)
end
return
end
end
timer.Simple(0.055, function()
if (IsValid(self) and IsValid(self:GetOwner())) then
local damage = self.Primary.Damage
if ix.plugin.Get("vortigaunts") then
if self:GetOwner():IsVortigaunt() then
damage = damage + self:GetOwner():GetCharacter():GetSkillLevel("melee")
end
if (SERVER and self:GetOwner():IsPlayer() and self:GetOwner().GetCharacter and self:GetOwner():GetCharacter() and self:GetOwner():IsVortigaunt()) then
self:GetOwner():GetCharacter():DoAction("melee_slash")
end
end
local context = {damage = damage}
local result = hook.Run("GetPlayerPunchDamage", self:GetOwner(), damage, context)
if (result != nil) then
damage = result
else
damage = context.damage
end
damage = damage * 1.7 --For some reason, punching only does 60% of the damage. 60% * 1.7 = 102%
if self:GetOwner():IsVortigaunt() and damage > 25 then
damage = 30
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
if ix.plugin.Get("vortigaunts") then
if (self:GetOwner():IsPlayer() and self:GetOwner().GetCharacter and self:GetOwner():GetCharacter() and self:GetOwner():IsVortigaunt()) then
self:GetOwner():GetCharacter():DoAction("melee_hit")
end
end
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)
if (entity:IsPlayer()) then
ix.log.AddRaw(self:GetOwner():Name() .." has damaged " .. entity:Name() .. ", dealing " .. damage .. " with ix_hands")
end
if (entity:IsNPC()) then
ix.log.AddRaw(self:GetOwner():Name() .." has damaged " .. entity:GetClass() .. ", dealing " .. damage .. " with ix_hands")
end
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)
if (CLIENT and trace.Hit) then
local entity = trace.Entity
--This is kind of redundant
--if (entity:IsPlayer() and IsValid(entity)) then
-- chat.AddText(Color(217, 83, 83), "You have damaged " .. entity:Name() .. ", dealing " .. damage .. " points of damage.")
--end
--if (entity:IsNPC()) then
-- chat.AddText(Color(217, 83, 83), "You have damaged " .. entity:GetClass() .. ", dealing " .. damage .. " points of damage.")
--end
end
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
self:PushEntity(entity)
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:PushEntity(entity)
local curTime = CurTime()
local direction = self.Owner:GetAimVector() * (300 + (self.Owner:GetCharacter():GetAttribute("str", 0) * 3))
direction.z = 0
entity:SetVelocity(direction)
self.Owner:EmitSound("physics/flesh/flesh_impact_hard"..math.random(1, 6)..".wav")
self:SetNextSecondaryFire(curTime + 1.5)
self:SetNextPrimaryFire(curTime + 1.5)
end
function SWEP:Reload()
if (!IsFirstTimePredicted()) then
return
end
if (SERVER and IsValid(self.heldEntity)) then
self:DropObject()
end
end

View File

@@ -0,0 +1,60 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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")

View File

@@ -0,0 +1,244 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- You can change the default language by setting this in your schema.
ix.config.language = "polish"
--[[
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("descriptionDisplayLength", 256,
"The amount of characters of a description that will be displayed when someone look at the player.", nil, {
data = {min = 64, max = 2048},
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 ipairs(player.GetAll()) 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 ipairs(player.GetAll()) 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("jumpPower", 200, "How much force is behind ones jump.", function(oldValue, newValue)
for _, v in ipairs(player.GetAll()) do
v:SetJumpPower(newValue)
end
end, {
data = {min = 100, max = 400},
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://willard.network/forums/",
"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("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("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("pushOnly", false, "Whether or not punch damage is enabled.", 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"
})

View File

@@ -0,0 +1,75 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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("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"
})
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
})

View File

@@ -0,0 +1,608 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- luacheck: globals VerticalScale VScale ScreenScaleMin SScaleMin
function VerticalScale( size )
return size * ( ScrH() / 360.0 )
end
VScale = VerticalScale
function ScreenScaleMin( size )
return math.min(SScale(size), VScale(size))
end
SScaleMin = ScreenScaleMin
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:PaintScrollBarGrip(panel, width, height)
local parent = panel:GetParent()
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)
end
function SKIN:PaintButtonUp(panel, width, height)
end
function SKIN:PaintButtonDown(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
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()

View File

@@ -0,0 +1,174 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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.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() * 15)
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")

View File

@@ -0,0 +1,207 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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

View File

@@ -0,0 +1,513 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)

View File

@@ -0,0 +1,551 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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
bValue = self.bUsingCharacter
end
self.returnButton:SetText(bValue 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(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
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

View File

@@ -0,0 +1,509 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,486 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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:SetMouseInputEnabled(false)
self:Slide("down", 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")

View File

@@ -0,0 +1,169 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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 = table.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)

View File

@@ -0,0 +1,210 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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))
-- 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, _ 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:SetText(k)
row:SetValue(false, true)
row.OnValueChanged = function(panel, bEnabled)
self:OnPluginToggled(k, bEnabled)
end
end
if (!bNoSizeToContents) then
self:SizeToContents()
end
end
vgui.Register("ixPluginManager", PANEL, "ixSettings")

View File

@@ -0,0 +1,318 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)

View File

@@ -0,0 +1,271 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--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"
.." pos = Vector("..math.Round(camPos.x, 2)..", "..math.Round(camPos.y, 2)..", "..math.Round(camPos.z, 2).."),\n"
.." ang = Angle("..math.Round(camAng.p, 2)..", "..math.Round(camAng.y, 2)..", "..math.Round(camAng.r, 2).."),\n"
.." fov = "..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)

View File

@@ -0,0 +1,287 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,933 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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")

View File

@@ -0,0 +1,379 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)

View File

@@ -0,0 +1,283 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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 = "%A, %B %d, %Y. %H:%M"
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)

View File

@@ -0,0 +1,378 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,806 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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 _, v in pairs(inventory:GetItems()) do
if (!v.isBag) then
continue
end
v.functions.View.OnClick(v)
end
end
canvas.PerformLayout = canvasLayout
canvas:Layout()
end
}
end)
hook.Add("PostRenderVGUI", "ixInvHelper", function()
local pnl = ix.gui.inv1
hook.Run("PostDrawInventory", pnl)
end)

View File

@@ -0,0 +1,501 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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("Panel")
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()
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

View File

@@ -0,0 +1,305 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,130 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,280 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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

View File

@@ -0,0 +1,98 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,253 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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)

View File

@@ -0,0 +1,359 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)

View File

@@ -0,0 +1,781 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)

View File

@@ -0,0 +1,130 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,107 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,207 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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")

View File

@@ -0,0 +1,312 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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(L(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
self.childPanels[i]:PaintManual()
end
end
function PANEL:PaintSubpanels(width, height)
for i = 1, #self.subpanels do
self.subpanels[i]:PaintManual()
end
end
-- ????
PANEL.Remove = BaseClass.Remove
vgui.Register("ixSubpanelParent", PANEL, "EditablePanel")

View File

@@ -0,0 +1,604 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--- 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 diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,672 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)
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 = 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
if (banned < os.time()) then
return
end
return false, "@charBannedTemp"
end
return false, "@charBanned"
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
-- I'm sorry. ~Aspect
--[[
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 _, v in pairs(inventory:GetItems()) do
if (v: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)
local slots = ix.plugin.list["inventoryslots"]
if slots then return slots:CanEquipOrUnequip(client, item, true) end
return item.invID == client:GetCharacter():GetInventory():GetID()
end
function GM:CanPlayerUnequipItem(client, item)
local slots = ix.plugin.list["inventoryslots"]
if slots then return slots:CanEquipOrUnequip(client, item, false) end
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

View File

@@ -0,0 +1,951 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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 model = faction and table.Random(faction:GetModels(client)) or "models/gman.mdl"
if (istable(model)) then
model = table.Random(model)
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 ipairs(player.GetAll()) 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:CanPlayerInteractItem(client, action, item)
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 (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: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)) 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 ipairs(player.GetAll()) 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 ipairs(player.GetAll()) 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:SetJumpPower(ix.config.Get("jumpPower"))
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 _, v in pairs(character:GetInventory():GetItems()) do
v:Call("OnLoadout", client)
if (v:GetData("equip") and v.attribBoosts) then
for attribKey, attribValue in pairs(v.attribBoosts) do
character:AddBoost(v.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
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 ipairs(player.GetAll()) 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 ipairs(player.GetAll()) 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)
-- Object is already frozen (!?)
if (entity.scaledSize) then return true end
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 _, v in pairs(character:GetInventory():GetItems()) do
if (v.OnSave) then
v: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 ipairs(player.GetAll()) 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)
net.Receive("ixConfirmationRequest", function(length, client)
local time = net.ReadUInt(32)
local confirmation = net.ReadBool()
if (client.ixConfReqs and client.ixConfReqs[time]) then
client.ixConfReqs[time](confirmation)
client.ixConfReqs[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

View File

@@ -0,0 +1,131 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)

View File

@@ -0,0 +1,146 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
ix.hud = {}
local owner, w, h, ceil, ft, clmp
ceil = math.ceil
clmp = math.Clamp
local aprg, aprg2 = 0, 0
function ix.hud.DrawDeath()
owner = LocalPlayer()
ft = FrameTime()
w, h = ScrW(), ScrH()
if (owner:GetCharacter()) then
if (owner:Alive()) then
if (aprg != 0) then
aprg2 = clmp(aprg2 - ft*1.3, 0, 1)
if (aprg2 == 0) then
aprg = clmp(aprg - ft*.7, 0, 1)
end
end
else
if (aprg2 != 1) then
aprg = clmp(aprg + ft*.5, 0, 1)
if (aprg == 1) then
aprg2 = clmp(aprg2 + ft*.4, 0, 1)
end
end
end
end
if (IsValid(ix.gui.characterMenu) and ix.gui.characterMenu:IsVisible() or !owner:GetCharacter()) then
return
end
surface.SetDrawColor(0, 0, 0, ceil((aprg^.5) * 255))
surface.DrawRect(-1, -1, w+2, h+2)
ix.util.DrawText(
string.utf8upper(L"youreDead"), w/2, h/2, ColorAlpha(color_white, aprg2 * 255), 1, 1, "ixMenuButtonHugeFont", aprg2 * 255
)
end
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
hook.Add("GetCrosshairAlpha", "ixCrosshair", function(alpha)
return alpha * (1 - aprg)
end)
function ix.hud.DrawAll(postHook)
if (postHook) then
ix.hud.DrawDeath()
end
ix.hud.DrawItemPickup()
end

View File

@@ -0,0 +1,506 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- 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("<font=Default>changed font</font>\n<colour=255,0,255,255>changed colour</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 .. "<nop>"
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, "&gt;", ">")
block.text = string.gsub(block.text, "&lt;", "<")
block.text = string.gsub(block.text, "&amp;", "&")
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

View File

@@ -0,0 +1,68 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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("ixNetStatics", function()
for _ = 1, net.ReadUInt(16) do
local id = net.ReadUInt(16)
if (id == 0) then continue end
ix.net[id] = ix.net[id] or {}
ix.net[id].Persistent = true
end
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

View File

@@ -0,0 +1,160 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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

View File

@@ -0,0 +1,571 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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_PISTOL, ACT_IDLE_ANGRY_PISTOL},
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_ATTACK_PISTOL_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_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.metrocop_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.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] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
},
grenade = {
["attack"] = ACT_MELEE_ATTACK1,
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}
},
normal = {
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
["attack"] = ACT_MELEE_ATTACK1
},
pistol = { -- beam
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
["attack"] = ACT_GESTURE_RANGE_ATTACK_PISTOL,
["reload"] = ACT_IDLE,
["glide"] = {ACT_RUN, ACT_RUN}
},
shotgun = { -- broom
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "sweep_idle"},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_WALK] = {"Walk_all_HoldBroom", "Walk_all_HoldBroom"},
["attack"] = ACT_IDLE,
["reload"] = ACT_IDLE,
["glide"] = {ACT_RUN, ACT_RUN}
},
smg = { -- heal
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
["attack"] = ACT_IDLE,
["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

View File

@@ -0,0 +1,201 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- @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

View File

@@ -0,0 +1,161 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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
if entity.itemData then
local item, _ = client:GetCharacter():GetInventory():Add(uniqueID, 1, {
data = entity.itemData
})
if (!item) then
return client:NotifyLocalized("noFit")
end
else
local status, _ = client:GetCharacter():GetInventory():Add(uniqueID)
if (!status) then
return client:NotifyLocalized("noFit")
end
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 diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,758 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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"
})
CAMI.RegisterPrivilege({
Name = "Helix - OOC See IC Name",
MinAccess = "admin"
})
CAMI.RegisterPrivilege({
Name = "Helix - Icon Incognito Mode",
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\""
data.icon = data.icon or nil
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
if self.bigfont then
local oldFont = self.font
local font = hook.Run("GetSpeakerYellFont", speaker)
self.font = font
elseif self.specialfont then
local oldFont = self.font
local font = hook.Run("GetSpeakerFont", speaker)
self.font = font
else
local oldFont = self.font
local font = "ixChatFont"
self.font = font
end
local translated = L2(chatType.."Format", name, text)
if self.icon and ix.option.Get("standardIconsEnabled") then
chat.AddText(ix.util.GetMaterial(self.icon), color, translated or string.format(self.format, name, text))
else
chat.AddText(color, translated or string.format(self.format, name, text))
end
if self.font then
self.font = oldFont
end
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, receivers, data) == 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 ipairs(player.GetAll()) 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")
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
local iconIncognitoMode = false
if speaker and IsValid(speaker) and !speaker:IsBot() then
iconIncognitoMode = ix.option.Get(speaker, "iconIncognitoMode", false)
end
text = hook.Run("PlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, rawText, data) or text
net.Start("ixChatMessage")
net.WriteEntity(speaker)
net.WriteString(chatType)
net.WriteString(text)
net.WriteBool(bAnonymous or false)
net.WriteBool(iconIncognitoMode)
net.WriteTable(data or {})
net.Send(receivers)
return text
end
end
else
function ix.chat.Send(speaker, chatType, text, anonymous, data, iconIncognitoMode)
local class = ix.chat.classes[chatType]
if (class) then
-- luacheck: globals CHAT_CLASS
CHAT_CLASS = class
class:OnChatAdd(speaker, text, anonymous, data, iconIncognitoMode)
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 iconIncognitoMode = net.ReadBool()
local data = net.ReadTable()
local info = {
chatType = chatType,
text = text,
anonymous = anonymous,
iconIncognitoMode = iconIncognitoMode,
data = data
}
if (IsValid(client)) then
hook.Run("MessageReceived", client, info)
ix.chat.Send(client, info.chatType or chatType, info.text or text, info.anonymous or anonymous, info.data,
info.iconIncognitoMode)
else
if client and client.IsWorld and client:IsWorld() then
hook.Run("MessageReceived", nil, info)
end
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\"",
icon = "willardnetworks/chat/message_icon.png",
indicator = "chatTalking",
color = Color(255, 254, 153, 255),
CanHear = ix.config.Get("chatRange", 280)
})
ix.option.Add("iconIncognitoMode", ix.type.bool, false, {
bNetworked = true,
category = "chat",
hidden = function()
return !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Icon Incognito Mode")
end
})
if (CLIENT) then
ix.option.Add("standardIconsEnabled", ix.type.bool, true, {
category = "chat"
})
ix.option.Add("seeGlobalOOC", ix.type.bool, true, {
category = "chat"
})
ix.option.Add("enablePrivateMessageSound", ix.type.bool, true, {
category = "chat"
})
end
-- Actions and such.
ix.chat.Register("me", {
format = "*** %s %s",
color = Color(214, 254, 137, 255),
CanHear = ix.config.Get("chatRange", 280) * 2,
prefix = {"/Me", "/Action"},
description = "@cmdMe",
indicator = "chatPerforming",
CanSay = function(self, speaker, text)
if (!speaker:Alive() and speaker.lastMeExpended) then
speaker:NotifyLocalized("noPerm")
return false
end
end
})
-- 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\"",
icon = "willardnetworks/chat/whisper_icon.png",
color = Color(158, 162, 191, 255),
CanHear = ix.config.Get("chatRange", 280) * 0.25,
prefix = {"/W", "/Whisper"},
description = "@cmdW",
indicator = "chatWhispering",
specialfont = true
})
-- Yelling out loud.
ix.chat.Register("y", {
format = "%s yells \"%s\"",
color = Color(254, 171, 103, 255),
icon = "willardnetworks/chat/yell_icon.png",
CanHear = ix.config.Get("chatRange", 280) * 2,
prefix = {"/Y", "/Yell"},
description = "@cmdY",
indicator = "chatYelling",
bigfont = true
})
-- REMEMBER TO UPDATE THESE WHEN WE CHANGE RANKS IN SAM
local chatIconMap = {
["willardnetworks/chat/star.png"] = {
["community_manager"] = true,
["head_of_staff"] = true,
["server_council"] = true,
["willard_management"] = true,
["superadmin"] = true
},
["willardnetworks/chat/hammer.png"] = {
["senior_admin"] = true,
["server_admin"] = true,
["trial_admin"] = true,
["admin"] = true
},
["willardnetworks/chat/paintbrush.png"] = {
["gamemaster"] = true
},
["willardnetworks/chat/wrench.png"] = {
["developer"] = true
},
["willardnetworks/chat/leaf.png"] = {
["mentor"] = true
},
["willardnetworks/chat/heart.png"] = {
["premium1"] = true,
["premium2"] = true,
["premium3"] = true
}
}
local function GetIcon(speaker, iconIncognitoMode)
local icon = "willardnetworks/chat/ooc_icon.png"
if !speaker or speaker and !IsValid(speaker) then
return ix.util.GetMaterial(icon)
end
if iconIncognitoMode then return ix.util.GetMaterial(icon) end
for mat, rankGroup in pairs(chatIconMap) do
if (rankGroup[speaker:GetUserGroup()]) then
icon = mat
end
end
return ix.util.GetMaterial(hook.Run("GetPlayerIcon", speaker) or icon)
end
-- Out of character.
ix.chat.Register("ooc", {
CanSay = function(self, speaker, text)
if (!ix.config.Get("allowGlobalOOC")) then
speaker:NotifyLocalized("GOOCIsDisabled")
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, _, _, iconIncognitoMode)
-- @todo remove and fix actual cause of speaker being nil
if (!IsValid(speaker) or !ix.option.Get("seeGlobalOOC", true)) then
return
end
local icon = GetIcon(speaker, iconIncognitoMode)
if (CAMI.PlayerHasAccess(LocalPlayer(), "Helix - OOC See IC Name")) then
chat.AddText(icon, Color(255, 66, 66), "[OOC] ", Color(192, 192, 196), speaker:SteamName()
, " (", speaker:Name(), ")", color_white, ": ", text)
else
chat.AddText(icon, Color(255, 66, 66), "[OOC] ", Color(192, 192, 196), speaker:SteamName(), color_white, ": ", text)
end
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, _, _, iconIncognitoMode)
local icon = GetIcon(speaker, iconIncognitoMode)
local name = hook.Run("GetCharacterName", speaker, "ic") or
(IsValid(speaker) and speaker:Name() or "Console")
chat.AddText(icon, Color(255, 66, 66), "[LOOC] ", Color(255, 254, 153, 255), 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 = "%s (%s): %s",
color = Color(255, 255, 239, 61),
deadCanChat = true,
OnChatAdd = function(self, speaker, text, bAnonymous, data)
local client = LocalPlayer()
if (ix.option.Get("standardIconsEnabled")) then
if (speaker and client == speaker) then
if !data.target or data.target and !IsValid(data.target) then return end
chat.AddText(ix.util.GetMaterial("willardnetworks/chat/pm_icon.png"), Color(254, 238, 60), "[PM] »"
, self.color, string.format(self.format, data.target:GetName(), data.target:SteamName(), text))
else
chat.AddText(ix.util.GetMaterial("willardnetworks/chat/pm_icon.png"), Color(254, 238, 60), "[PM] "
, self.color, string.format(self.format, speaker:GetName(), speaker:SteamName(), text))
end
else
if (client == speaker) then
chat.AddText(Color(254, 238, 60), "[PM] »"
, self.color, string.format(self.format, data.target:GetName(), data.target:SteamName(), text))
else
chat.AddText(Color(254, 238, 60), "[PM] "
, self.color, string.format(self.format, speaker:GetName(), speaker:SteamName(), text))
end
end
if (client != speaker and ix.option.Get("enablePrivateMessageSound", true)) 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(254, 138, 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("willardnetworks/chat/connected_icon.png")
chat.AddText(icon, Color(151, 153, 152), 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("willardnetworks/chat/disconnected_icon.png")
chat.AddText(icon, Color(151, 153, 152), 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")

View File

@@ -0,0 +1,212 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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
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
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
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
--- Retrieves a class table.
-- @realm shared
-- @number identifier Index of the class
-- @treturn table Class table
function ix.class.Get(identifier)
return ix.class.list[identifier]
end
--- Retrieves the players in a class
-- @realm shared
-- @number class Index of the class
-- @treturn table Table of players in the class
function ix.class.GetPlayers(class)
local players = {}
for _, v in ipairs(player.GetAll()) 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
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

View File

@@ -0,0 +1,636 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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 `"<target: character> [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
if argument == "^" then
value = client
else
value = ix.util.FindPlayer(argument)
end
-- 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 = "<none>"
end
else
data.syntax = data.syntax or "<none>"
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
end

View File

@@ -0,0 +1,166 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
ix.compnettable = ix.compnettable or {}
-- size of chunks
ix.compnettable.chunkSize = 60000
do
if (CLIENT) then
ix.compnettable.doExpoBackOff = true
-- expo backoff and jitter vars
ix.compnettable.maxBackOffTime = 20 -- tenth of a second
ix.compnettable.baseBackOffTime = 1 -- tenth of a second
ix.compnettable.attemptsInWindow = 0
ix.compnettable.windowSize = 1 -- seconds
ix.compnettable.lastRequest = CurTime()
else
-- don't do backoffs on the server (that'd break a lot of shit)
ix.compnettable.doExpobackOff = false
end
end
-- writes a compressed and chunked table with the following mechanism:
--[[
1) sends an int of the number of chunks to expect
2) writes the chunks as compressed strings
3) returns number of chunks to caller
IMPORTANT
THIS MUST BE USED IN THE CORRECT CONTEXT. EX:
net.Send("YourNetHook", function()
ix.compnettable:Write(myTable)
end)
net.Receive("YourNetHook", function()
local yourTable = ix.compnettable:Read()
end)
OTHERWISE IT WILL BREAK YOUR SHIT
]]
function ix.compnettable:Write(tbl)
if (!istable(tbl)) then
return nil
end
local str = util.TableToJSON(tbl)
local basestrlen = string.len(str)
if (!str or basestrlen < 1) then
error("Provided table could not be converted to JSON!")
end
local nchunks = math.ceil(basestrlen / self.chunkSize)
net.WriteInt(nchunks, 17)
for i=0, nchunks + 1, 1 do
local stringslice = string.sub(
str,
i * self.chunkSize,
(i + 1) *self.chunkSize
)
local chunk = util.Compress(stringslice)
if (!chunk) then
error(
"Chunk "
..tostring(i)
.." cannot be compressed! Attempted to compress: "
..stringslice
)
end
net.WriteInt(#chunk, 17)
net.WriteData(chunk, #chunk)
end
return nchunks
end
-- attempts to read a table compressed by ix.compnettable.Write()
--[[ IMPORTANT
THIS MUST BE USED IN THE CORRECT CONTEXT. EX:
net.Send("YourNetHook", function()
ix.compnettable:Write(myTable)
end)
net.Receive("YourNetHook", function()
local yourTable = ix.compnettable:Read()
end)
OTHERWISE IT WILL BREAK YOUR SHIT
]]
function ix.compnettable:Read()
local nchunks = net.ReadInt(17)
if (nchunks < 1) then
error("No chunks to read!")
end
local fullstr = ""
for i=0, nchunks + 1, 1 do
local chunksize = net.ReadInt(17)
local strcomp = net.ReadData(chunksize)
if (#strcomp == 1 and strcomp[0] == 0) then
error("Expected substring at idx ("..tostring(i)..") out of ("..tostring(nchunks)..") is empty or nil!")
end
local strchunk = util.Decompress(strcomp)
if (!strchunk) then
error("Substring ("..tostring(i)..") out of ("..tostring(nchunks)..") failed to decompress!")
end
fullstr = fullstr..strchunk
end
return util.JSONToTable(fullstr)
end
-- reads with a brief backoff to help with overflow issues
-- https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
-- ^^ a cheap (and wildly simplified) version of this:
function ix.compnettable:ReadWithBackoff()
if (CLIENT) then
local backOffTime = 0
local reqStart = SysTime()
if (self.doExpoBackOff) then
if ((reqStart - self.lastRequest) < self.windowSize) then
self.attemptsInWindow = self.attemptsInWindow + 1
local minBackOffTime = math.min(
math.pow(
self.baseBackOffTime,
self.attemptsInWindow
),
self.maxBackOffTime
)
local maxBackOffTime = minBackOffTime + (self.baseBackOffTime * 10)
backOffTime = math.Rand(minBackOffTime, maxBackOffTime) / 10
else
self.attemptsInWindow = 0
end
end
if (backOffTime > 0) then
print("Backing off for "..tostring(backOffTime).." seconds...")
-- ^^ leave this in for now just in case ;)
end
local sec = tonumber(SysTime() + backOffTime);
while (SysTime() < sec) do
-- wait for backOffTime in seconds...
-- NOTE: This might cause some freezing and things
-- BUT A FREEZE IS BETTER THAN AN OVERFLOW!!!!!!!!
end
self.lastRequest = reqStart
end
return self:Read()
end

View File

@@ -0,0 +1,121 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--- 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

View File

@@ -0,0 +1,156 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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

View File

@@ -0,0 +1,135 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--- 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

View File

@@ -0,0 +1,213 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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

View File

@@ -0,0 +1,186 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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
if invType == "equipInventory" then
inventory.vars.isBag = false
else
inventory.vars.isBag = invType
end
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

View File

@@ -0,0 +1,879 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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
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
}
ITEM.functions.removeLabel = ITEM.functions.removeLabel or {
name = "Remove Label",
icon = "icon16/tag_blue_delete.png",
OnRun = function(item)
local client = item.player
local inventory = client:GetCharacter():GetInventory()
inventory:Add("itemlabel", 1, {
labelInfoName = item:GetData("labelName"),
labelInfoDesc = item:GetData("labelDescription")
})
item:SetData("labelName", false)
item:SetData("labelDescription", false)
client:Notify("You remove the label from the " .. item.name .. ".")
return false
end,
OnCanRun = function(item)
return item:GetData("labelName", false) or item:GetData("labelDescription", false)
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
table.Merge(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, item:GetModel() or "models/props_junk/popcan01a.mdl",
x, y, item.width, item.height, item:GetSkin(), item:GetModelBodygroups(), item.color, item.material, item.rotate, item.OnInventoryDraw)
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, callback)
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
if (callback) then
callback(item)
end
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 (item.entity:GetPos():Distance(client:GetPos()) > 96) 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 or inventory == nil) then
inventory:Sync(client)
end
if ((!inventory.owner or (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 hook.Run("CanMoveItemSameInv", item, inventory, x, y) == false then 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"
})

View File

@@ -0,0 +1,138 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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/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, arg, ...)
local defaultLang = ix.config.language or "polish"
local languages = ix.lang.stored
if (IsEntity(arg) and arg:IsPlayer()) then
local langKey = ix.option.Get(arg, "language", defaultLang)
local info = languages[langKey] or languages[defaultLang]
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, ...)
else
local info = languages[defaultLang]
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, arg, ...)
end
end
-- luacheck: globals L2
function L2(key, arg, ...)
local defaultLang = ix.config.language or "polish"
local languages = ix.lang.stored
if (IsEntity(arg) and arg:IsPlayer()) then
local langKey = ix.option.Get(arg, "language", defaultLang)
local info = languages[langKey] or languages[defaultLang]
if (info and info[key]) then
return string.format(info[key], ...)
end
else
local info = ix.lang.stored[defaultLang]
if (info and info[key]) then
return string.format(info[key], arg, ...)
end
end
end
else
function L(key, ...)
local defaultLang = ix.config.language or "polish"
local languages = ix.lang.stored
local langKey = ix.option.Get("language", defaultLang)
local info = languages[langKey] or languages[defaultLang]
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, ...)
end
function L2(key, ...)
local defaultLang = ix.config.language or "polish"
local langKey = ix.option.Get("language", defaultLang)
local info = ix.lang.stored[langKey]
if (info and info[key]) then
return string.format(info[key], ...)
end
end
end

View File

@@ -0,0 +1,169 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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 {}
--- Adds a log type
-- @realm server
-- @string logType Log category
-- @string format The string format that log messages should use
-- @number flag Log level
function ix.log.AddType(logType, format, flag)
ix.log.types[logType] = {format = format, flag = flag}
end
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) .. "\"")
return
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
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
--- Add a log message
-- @realm server
-- @player client Player who instigated the log
-- @string logType Log category
-- @param ... Arguments to pass to the log
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
function ix.log.Send(client, logString, flag)
net.Start("ixLogStream")
net.WriteString(logString)
net.WriteUInt(flag or 0, 4)
net.Send(client)
end
ix.log.handlers = ix.log.handlers or {}
function ix.log.CallHandler(event, ...)
for _, v in pairs(ix.log.handlers) do
if (isfunction(v[event])) then
v[event](...)
end
end
end
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

View File

@@ -0,0 +1,119 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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 or
entity:GetPos():Distance(client:GetPos()) > 96) 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

View File

@@ -0,0 +1,154 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--- 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)
ix.chat.Send(nil, "notice", message, false, {self})
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, ...)
ix.chat.Send(nil, "notice", L(message, self, ...), false, {self})
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
ix.chat.Send(LocalPlayer(), "notice", message)
end
end
function playerMeta:ChatNotifyLocalized(message, ...)
if (self == LocalPlayer()) then
ix.chat.Send(LocalPlayer(), "notice", L(message, ...))
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

View File

@@ -0,0 +1,381 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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 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
--- 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 = "opt" .. upperName,
description = "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
--- 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
-- @bool[opt=false] bNoNetwork Whether or not to avoid networking regardless of whether the option is networked or not
function ix.option.Set(key, value, bNoSave, bNoNetwork)
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 and !bNoNetwork) 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
net.Receive("ixOptionSet", function()
local key = net.ReadString()
local value = net.ReadType()
local bNoSave = net.ReadBool()
ix.option.Set(key, value, bNoSave, true)
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
function ix.option.Set(client, key, value, bNoSave)
local steamID = client:SteamID64()
local option = ix.option.stored[key]
if (option) then
if (option.bNetworked) then
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
ix.option.clients[steamID][key] = value
end
net.Start("ixOptionSet")
net.WriteString(key)
net.WriteType(value)
net.WriteBool(bNoSave)
net.Send(client)
else
ErrorNoHalt(string.format("Attempted to set option with invalid key '%s' for '%s'\n", key, tostring(client) .. client:SteamID()))
end
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

View File

@@ -0,0 +1,153 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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

View File

@@ -0,0 +1,526 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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 = {}
local function insertSorted(tbl, plugin, func, priority)
if (IX_RELOADED) then
-- Clean out the old function from the table
for i = 1, #tbl do
if (tbl[i][1] == plugin) then
table.remove(tbl, i)
break
end
end
end
-- Attempt to insert into an empty table or at the end first
if (#tbl == 0 or tbl[#tbl][3] >= priority) then
tbl[#tbl + 1] = {plugin, func, priority}
return
end
-- Find where to insert
for i = #tbl - 1, 1, -1 do
if (tbl[i][3] >= priority) then
table.insert(tbl, i + 1, {plugin, func, priority})
return
end
end
-- Insert at the front
table.insert(tbl, 1, {plugin, func, priority})
end
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."
PLUGIN.hookCallPriority = PLUGIN.hookCallPriority or 1000
for k, v in pairs(PLUGIN) do
if (isfunction(v)) then
HOOKS_CACHE[k] = HOOKS_CACHE[k] or {}
insertSorted(HOOKS_CACHE[k], PLUGIN, v,
(PLUGIN.GetHookCallPriority and PLUGIN:GetHookCallPriority(k))
or PLUGIN.hookCallPriority)
end
end
ix.plugin.list[uniqueID] = PLUGIN
_G[variable] = nil
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
for _, v in ipairs(h) do
if (v[1] == p) then
return v[2]
end
end
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()
ix.plugin.unloaded = ix.data.Get("unloaded", {}, true, true)
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] = nil
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
local hookCall
if (CLIENT) then
hookCall = function(name, gm, ...)
local cache = HOOKS_CACHE[name]
if (cache) then
for i = 1, #cache do
local a, b, c, d, e, f = cache[i][2](cache[i][1], ...)
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
--luacheck: ignore global SwiftAC__N
if (SwiftAC__N) then
SwiftAC__N.hook.Call(name, gm, ...)
else
return hook.ixCall(name, gm, ...)
end
end
--SwiftAC compatibility
hook.Run = function(name, ...)
return hookCall(name, gmod and gmod.GetGamemode(), ...)
end
else
hookCall = function(name, gm, ...)
local cache = HOOKS_CACHE[name]
if (cache) then
for i = 1, #cache do
local a, b, c, d, e, f = cache[i][2](cache[i][1], ...)
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
end
hook.Call = hookCall
--- 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 i = 1, #cache do
local bSuccess, a, b, c, d, e, f = pcall(cache[i][2], cache[i][1], ...)
if (bSuccess) then
if (a != nil) then
return errors, a, b, c, d, e, f
end
else
local plugin = cache[i][1]
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for plugin hook \"%s:%s\":\n\t%s\n%s\n",
tostring(plugin and plugin.uniqueID or nil), tostring(name), tostring(a), debug.traceback()))
errors[#errors + 1] = {
name = name,
plugin = plugin and plugin.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

View File

@@ -0,0 +1,491 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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 _, v in pairs(inventory:GetItems()) do
if (v.isBag and v:GetInventory()) then
ix.storage.CreateContext(v: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 _, v in pairs(inventory:GetItems()) do
if (v.isBag and v:GetInventory()) then
ix.storage.RemoveContext(v: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 _, v in pairs(inventory:GetItems()) do
if (v.isBag and v:GetInventory()) then
v: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 _, v in pairs(inventory:GetItems()) do
if (v.isBag and v:GetInventory()) then
v: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
hook.Run("OnStorageReceiverRemoved", client, inventory)
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 (hook.Run("CanTakeMoney", client, entity, amount) == false) then
client:NotifyLocalized("notAllowed")
return
end
local total
if (inventory.storageInfo.OnMoneyTake) then
amount, total = inventory.storageInfo.OnMoneyTake(client, inventory, amount)
if (!amount) then
return
end
else
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)
total = entity:GetMoney() - amount
entity:SetMoney(total)
ix.log.Add(client, "storageMoneyTake", entity, amount, total)
end
net.Start("ixStorageMoneyUpdate")
net.WriteUInt(storageID, 32)
net.WriteUInt(total, 32)
net.Send(inventory:GetReceivers())
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 (hook.Run("CanGiveMoney", client, entity, amount) == false) then
client:NotifyLocalized("notAllowed")
return
end
local total
if (inventory.storageInfo.OnMoneyGive) then
amount, total = inventory.storageInfo.OnMoneyGive(client, inventory, amount)
if (!amount) then
return
end
else
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)
total = entity:GetMoney() + amount
entity:SetMoney(total)
ix.log.Add(client, "storageMoneyGive", entity, amount, total)
end
net.Start("ixStorageMoneyUpdate")
net.WriteUInt(storageID, 32)
net.WriteUInt(total, 32)
net.Send(inventory:GetReceivers())
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 char = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
local localInventory = char and char:GetInventory()
local equipInvID = char and char.GetEquipInventory and char:GetEquipInventory()
local equipInv = equipInvID and ix.item.inventories[equipInvID]
local panel = vgui.Create("ixStorageView")
panel:CreateContents(inventory, entity)
if (localInventory) then
panel:SetLocalInventory(localInventory)
end
if (equipInv) then
panel:SetEquipInv(equipInv)
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

View File

@@ -0,0 +1,204 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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: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_characters_data")
query:Create("id", "INT(11) UNSIGNED NOT NULL")
query:Create("key", "VARCHAR(60) NOT NULL")
query:Create("data", "TEXT DEFAULT NULL")
query:PrimaryKey("id`,`key")
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)

View File

@@ -0,0 +1,245 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- @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("ixNetStatics")
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) 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
local division = 100
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
local statics = {}
local toWrite = {}
for entity, data in pairs(ix.net.list) do
if (IsValid(entity)) then
local index = entity:EntIndex()
if (table.Count(data) == 1 and data.Persistent == true) then
statics[#statics + 1] = index
continue
end
for k, v in pairs(data) do
toWrite[#toWrite + 1] = {index, k, v}
end
end
end
for i = 0, math.floor(#toWrite / division) do
timer.Simple(i * 0.5, function()
if (!IsValid(self)) then return end
for j = 0, division - 1 do
if (!toWrite[i * division + j]) then continue end
net.Start("ixNetVarSet")
net.WriteUInt(toWrite[i * division + j][1], 16)
net.WriteString(toWrite[i * division + j][2])
net.WriteType(toWrite[i * division + j][3])
net.Send(self)
end
end)
end
timer.Simple(30, function()
if (!IsValid(self)) then return end
net.Start("ixNetStatics")
net.WriteUInt(#statics, 16)
for _, v in ipairs(statics) do
local entity = Entity(v)
if (IsValid(entity) and ix.net.list[entity] and ix.net.list[entity].Persistent == true) then
net.WriteUInt(v, 16)
else
net.WriteUInt(0, 16)
end
end
net.Send(self)
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

View File

@@ -0,0 +1,125 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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)
hook.Run("PlayerDataRestored", self)
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()
hook.Run("PlayerDataRestored", self)
if (callback) then
callback({})
end
end
end)
query:Execute()
end
function playerMeta:SaveData()
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

View File

@@ -0,0 +1,362 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
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 pairs(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 diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,560 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
CAMI - Common Admin Mod Interface.
Makes admin mods intercompatible and provides an abstract privilege interface
for third party addons.
Follows the specification on this page:
https://docs.google.com/document/d/1QIRVcAgZfAYf1aBl_dNV_ewR6P25wze2KmUVzlbFgMI
Structures:
CAMI_USERGROUP, defines the charactaristics of a usergroup:
{
Name
string
The name of the usergroup
Inherits
string
The name of the usergroup this usergroup inherits from
}
CAMI_PRIVILEGE, defines the charactaristics of a privilege:
{
Name
string
The name of the privilege
MinAccess
string
One of the following three: user/admin/superadmin
Description
string
optional
A text describing the purpose of the privilege
HasAccess
function(
privilege :: CAMI_PRIVILEGE,
actor :: Player,
target :: Player
) :: bool
optional
Function that decides whether a player can execute this privilege,
optionally on another player (target).
}
]]
-- Version number in YearMonthDay format.
local version = 20190102
if CAMI and CAMI.Version >= version then return end
CAMI = CAMI or {}
CAMI.Version = version
--[[
usergroups
Contains the registered CAMI_USERGROUP usergroup structures.
Indexed by usergroup name.
]]
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
user = {
Name = "user",
Inherits = "user"
},
admin = {
Name = "admin",
Inherits = "user"
},
superadmin = {
Name = "superadmin",
Inherits = "admin"
}
}
--[[
privileges
Contains the registered CAMI_PRIVILEGE privilege structures.
Indexed by privilege name.
]]
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
--[[
CAMI.RegisterUsergroup
Registers a usergroup with CAMI.
Parameters:
usergroup
CAMI_USERGROUP
(see CAMI_USERGROUP structure)
source
any
Identifier for your own admin mod. Can be anything.
Use this to make sure CAMI.RegisterUsergroup function and the
CAMI.OnUsergroupRegistered hook don't cause an infinite loop
Return value:
CAMI_USERGROUP
The usergroup given as argument.
]]
function CAMI.RegisterUsergroup(usergroup, source)
usergroups[usergroup.Name] = usergroup
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
return usergroup
end
--[[
CAMI.UnregisterUsergroup
Unregisters a usergroup from CAMI. This will call a hook that will notify
all other admin mods of the removal.
Call only when the usergroup is to be permanently removed.
Parameters:
usergroupName
string
The name of the usergroup.
source
any
Identifier for your own admin mod. Can be anything.
Use this to make sure CAMI.UnregisterUsergroup function and the
CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
Return value:
bool
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
--[[
CAMI.GetUsergroups
Retrieves all registered usergroups.
Return value:
Table of CAMI_USERGROUP, indexed by their names.
]]
function CAMI.GetUsergroups()
return usergroups
end
--[[
CAMI.GetUsergroup
Receives information about a usergroup.
Return value:
CAMI_USERGROUP
Returns nil when the usergroup does not exist.
]]
function CAMI.GetUsergroup(usergroupName)
return usergroups[usergroupName]
end
--[[
CAMI.UsergroupInherits
Returns true when usergroupName1 inherits usergroupName2.
Note that usergroupName1 does not need to be a direct child.
Every usergroup trivially inherits itself.
Parameters:
usergroupName1
string
The name of the usergroup that is queried.
usergroupName2
string
The name of the usergroup of which is queried whether usergroupName
inherits from.
Return value:
bool
Whether usergroupName1 inherits usergroupName2.
]]
function CAMI.UsergroupInherits(usergroupName1, usergroupName2)
repeat
if usergroupName1 == usergroupName2 then return true end
usergroupName1 = usergroups[usergroupName1] and
usergroups[usergroupName1].Inherits or
usergroupName1
until not usergroups[usergroupName1] or
usergroups[usergroupName1].Inherits == usergroupName1
-- One can only be sure the usergroup inherits from user if the
-- usergroup isn't registered.
return usergroupName1 == usergroupName2 or usergroupName2 == "user"
end
--[[
CAMI.InheritanceRoot
All usergroups must eventually inherit either user, admin or superadmin.
Regardless of what inheritance mechism an admin may or may not have, this
always applies.
This method always returns either user, admin or superadmin, based on what
usergroups eventually inherit.
Parameters:
usergroupName
string
The name of the usergroup of which the root of inheritance is
requested
Return value:
string
The name of the root usergroup (either user, admin or superadmin)
]]
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
--[[
CAMI.RegisterPrivilege
Registers a privilege with CAMI.
Note: do NOT register all your admin mod's privileges with this function!
This function is for third party addons to register privileges
with admin mods, not for admin mods sharing the privileges amongst one
another.
Parameters:
privilege
CAMI_PRIVILEGE
See CAMI_PRIVILEGE structure.
Return value:
CAMI_PRIVILEGE
The privilege given as argument.
]]
function CAMI.RegisterPrivilege(privilege)
privileges[privilege.Name] = privilege
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
return privilege
end
--[[
CAMI.UnregisterPrivilege
Unregisters a privilege from CAMI. This will call a hook that will notify
all other admin mods of the removal.
Call only when the privilege is to be permanently removed.
Parameters:
privilegeName
string
The name of the privilege.
Return value:
bool
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
--[[
CAMI.GetPrivileges
Retrieves all registered privileges.
Return value:
Table of CAMI_PRIVILEGE, indexed by their names.
]]
function CAMI.GetPrivileges()
return privileges
end
--[[
CAMI.GetPrivilege
Receives information about a privilege.
Return value:
CAMI_PRIVILEGE when the privilege exists.
nil when the privilege does not exist.
]]
function CAMI.GetPrivilege(privilegeName)
return privileges[privilegeName]
end
--[[
CAMI.PlayerHasAccess
Queries whether a certain player has the right to perform a certain action.
Parameters:
actorPly
Player
The player of which is requested whether they have the privilege.
privilegeName
string
The name of the privilege.
callback
function(bool, string) or nil
This function will be called with the answer. The bool signifies the
yes or no answer as to whether the player is allowed. The string
will optionally give a reason.
Give an explicit nil here to get an answer immediately
Important note: May throw an error when the admin mod doesn't
give an answer immediately!
targetPly
Optional.
The player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
Fallback
string
Either of user/admin/superadmin. When no admin mod replies,
the decision is based on the admin status of the user.
Defaults to admin if not given.
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
Return value:
If callback is specified:
None
Otherwise:
hasAccess
bool
Whether the player has access
reason
Optional.
The reason why a player does or does not have access.
]]
-- Default access handler
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
function(_, actorPly, privilegeName, callback, _, 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
callback(
priv.MinAccess == "user" or
priv.MinAccess == "admin" and actorPly:IsAdmin() or
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
, "Fallback.")
end,
["CAMI.SteamIDHasAccess"] =
function(_, _, _, callback)
callback(false, "No information available.")
end
}
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
--[[
CAMI.GetPlayersWithAccess
Finds the list of currently joined players who have the right to perform a
certain action.
NOTE: this function will NOT return an immediate result!
The result is in the callback!
Parameters:
privilegeName
string
The name of the privilege.
callback
function(players)
This function will be called with the list of players with access.
targetPly
Optional.
The player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
Fallback
string
Either of user/admin/superadmin. When no admin mod replies,
the decision is based on the admin status of the user.
Defaults to admin if not given.
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
]]
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
--[[
CAMI.SteamIDHasAccess
Queries whether a player with a steam ID has the right to perform a certain
action.
Note: the player does not need to be in the server for this to
work.
Note: this function does NOT return an immediate result!
The result is in the callback!
Parameters:
actorSteam
Player
The SteamID of the player of which is requested whether they have
the privilege.
privilegeName
string
The name of the privilege.
callback
function(bool, string)
This function will be called with the answer. The bool signifies the
yes or no answer as to whether the player is allowed. The string
will optionally give a reason.
targetSteam
Optional.
The SteamID of the player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
Return value:
None, the answer is given in the callback function in order to allow
for the admin mod to perform e.g. a database lookup.
]]
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
targetSteam, extraInfoTbl)
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
privilegeName, callback, targetSteam, extraInfoTbl)
end
--[[
CAMI.SignalUserGroupChanged
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.
Parameters:
ply
Player
The player for which the usergroup is changed
old
string
The previous usergroup of the player.
new
string
The new usergroup of the player.
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
--[[
CAMI.SignalSteamIDUserGroupChanged
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.
Parameters:
ply
string
The steam ID of the player for which the usergroup is changed
old
string
The previous usergroup of the player.
new
string
The new usergroup of the player.
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

View File

@@ -0,0 +1,791 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
---------------------------------------------------------------------------------------
-- 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

View File

@@ -0,0 +1,193 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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

View File

@@ -0,0 +1,174 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
NetStream - 2.0.0
Alexander Grist-Hucker
http://www.revotech.org
Credits to:
thelastpenguin for pON.
https://github.com/thelastpenguin/gLUA-Library/tree/master/pON
--]]
AddCSLuaFile();
local _player = player
netstream = netstream or {};
netstream.stored = netstream.stored or {};
-- A function to split data for a data stream.
function netstream.Split(data)
local index = 1;
local result = {};
local buffer = {};
for i = 0, string.len(data) do
buffer[#buffer + 1] = string.sub(data, i, i);
if (#buffer == 32768) then
result[#result + 1] = table.concat(buffer);
index = index + 1;
buffer = {};
end;
end;
result[#result + 1] = table.concat(buffer);
return result;
end;
-- A function to hook a data stream.
function netstream.Hook(name, Callback)
netstream.stored[name] = Callback;
end;
if (SERVER) then
util.AddNetworkString("NetStreamDS");
-- A function to start a net stream.
function netstream.Start(player, name, ...)
local recipients = {};
local bShouldSend = false;
local bSendPVS = false;
if (type(player) != "table") then
if (!player) then
player = _player.GetAll();
elseif (type(player) == "Vector") then
bSendPVS = true;
else
player = {player};
end;
end;
if (type(player) != "Vector") then
for k, v in pairs(player) do
if (type(v) == "Player") then
recipients[#recipients + 1] = v;
bShouldSend = true;
elseif (type(k) == "Player") then
recipients[#recipients + 1] = k;
bShouldSend = true;
end;
end;
else
bShouldSend = true;
end;
local dataTable = {...};
local encodedData = pon.encode(dataTable);
if (encodedData and #encodedData > 0 and bShouldSend) then
net.Start("NetStreamDS");
net.WriteString(name);
net.WriteUInt(#encodedData, 32);
net.WriteData(encodedData, #encodedData);
if (bSendPVS) then
net.SendPVS(player);
else
net.Send(recipients);
end;
end;
end;
net.Receive("NetStreamDS", function(length, player)
local NS_DS_NAME = net.ReadString();
local NS_DS_LENGTH = net.ReadUInt(32);
local NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
player.nsDataStreamName = NS_DS_NAME;
player.nsDataStreamData = "";
if (player.nsDataStreamName and player.nsDataStreamData) then
player.nsDataStreamData = NS_DS_DATA;
if (netstream.stored[player.nsDataStreamName]) then
local bStatus, value = pcall(pon.decode, player.nsDataStreamData);
if (bStatus) then
netstream.stored[player.nsDataStreamName](player, unpack(value));
else
ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n");
end;
else
ErrorNoHalt("NetStream: Undefined hook for '"..NS_DS_NAME.."'\n")
end;
player.nsDataStreamName = nil;
player.nsDataStreamData = nil;
end;
end;
NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
end);
else
-- A function to start a net stream.
function netstream.Start(name, ...)
local dataTable = {...};
local encodedData = pon.encode(dataTable);
if (encodedData and #encodedData > 0) then
net.Start("NetStreamDS");
net.WriteString(name);
net.WriteUInt(#encodedData, 32);
net.WriteData(encodedData, #encodedData);
net.SendToServer();
end;
end;
net.Receive("NetStreamDS", function(length)
local NS_DS_NAME = net.ReadString();
local NS_DS_LENGTH = net.ReadUInt(32);
local NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
if (netstream.stored[NS_DS_NAME]) then
local bStatus, value = pcall(pon.decode, NS_DS_DATA);
if (bStatus) then
netstream.stored[NS_DS_NAME](unpack(value));
else
ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n");
end;
else
ErrorNoHalt("NetSteam: Undefined hook for '"..NS_DS_NAME.."'\n")
end;
end;
NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
end);
end;

View File

@@ -0,0 +1,411 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
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

View File

@@ -0,0 +1,385 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
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

View File

@@ -0,0 +1,338 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- $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

View File

@@ -0,0 +1,615 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
(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

View File

@@ -0,0 +1,663 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
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
if (isnumber(queryObj.offset)) then
queryString[#queryString + 1] = " OFFSET "
queryString[#queryString + 1] = queryObj.offset
end
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

View File

@@ -0,0 +1,397 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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
if (type(v.default) == "table") then
local tblQuery = mysql:Update("ix_characters_data")
tblQuery:Update("data", util.TableToJSON(self.vars[k]) or "NULL")
tblQuery:Where("id", self:GetID())
tblQuery:Where("key", v.field)
tblQuery:Execute()
else
local value = self.vars[k]
if (v.fieldType == ix.type.bool) then
value = value and 1 or 0
end
query:Update(v.field, tostring(value))
end
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 ipairs(player.GetAll()) 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")
ix.compnettable:Write(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")
ix.compnettable:Write(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()
hook.Run("OnCharacterKicked", self)
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)
hook.Run("OnCharacterBanned", self, time or true)
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 ipairs(player.GetAll()) do
if (v:SteamID64() == steamID) then
self.player = v
return v
end
end
end
end
function CHAR:GetFactionVar(variable, default)
local faction = ix.faction.Get(self:GetFaction())
if (!faction) then return end
return faction[variable] or default
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
if (type(data.default) != "table") then
ix.db.AddToSchema("ix_characters", data.field, data.fieldType or ix.type.string)
end
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

View File

@@ -0,0 +1,227 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--
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
local datatable = self:GetSaveTable()
if (datatable) then
return datatable.VehicleLocked
end
else
local datatable = self:GetSaveTable()
if (datatable) then
return datatable.m_bLocked
end
end
return false
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()
local datatable = self:GetSaveTable()
return datatable.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 ipairs(ents.GetAll()) 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 ipairs(ents.GetAll()) 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

Some files were not shown because too many files have changed in this diff Show More