mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 13:23:46 +03:00
Upload
This commit is contained in:
23
gamemodes/helix/LICENSE.txt
Normal file
23
gamemodes/helix/LICENSE.txt
Normal 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
72
gamemodes/helix/README.md
Normal 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
77
gamemodes/helix/config.ld
Normal 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")
|
||||
1
gamemodes/helix/docs/banner.gif
Normal file
1
gamemodes/helix/docs/banner.gif
Normal file
@@ -0,0 +1 @@
|
||||
GIF89a8h<01>
|
||||
|
After Width: | Height: | Size: 12 B |
96
gamemodes/helix/docs/css/highlight.css
Normal file
96
gamemodes/helix/docs/css/highlight.css
Normal 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;
|
||||
}
|
||||
522
gamemodes/helix/docs/css/ldoc.css
Normal file
522
gamemodes/helix/docs/css/ldoc.css
Normal 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;
|
||||
}
|
||||
52
gamemodes/helix/docs/hooks/class.lua
Normal file
52
gamemodes/helix/docs/hooks/class.lua
Normal 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
|
||||
58
gamemodes/helix/docs/hooks/faction.lua
Normal file
58
gamemodes/helix/docs/hooks/faction.lua
Normal 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
|
||||
938
gamemodes/helix/docs/hooks/plugin.lua
Normal file
938
gamemodes/helix/docs/hooks/plugin.lua
Normal 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
|
||||
168
gamemodes/helix/docs/js/app.js
Normal file
168
gamemodes/helix/docs/js/app.js
Normal 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);
|
||||
}
|
||||
});
|
||||
2
gamemodes/helix/docs/js/highlight.min.js
vendored
Normal file
2
gamemodes/helix/docs/js/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
329
gamemodes/helix/docs/manual/converting-from-clockwork.md
Normal file
329
gamemodes/helix/docs/manual/converting-from-clockwork.md
Normal 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)
|
||||
75
gamemodes/helix/docs/manual/getting-started.md
Normal file
75
gamemodes/helix/docs/manual/getting-started.md
Normal 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.
|
||||
```
|
||||
19
gamemodes/helix/docs/templates/landing.ltp
vendored
Normal file
19
gamemodes/helix/docs/templates/landing.ltp
vendored
Normal 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
90
gamemodes/helix/docs/templates/ldoc.ltp
vendored
Normal 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>
|
||||
123
gamemodes/helix/docs/templates/module.ltp
vendored
Normal file
123
gamemodes/helix/docs/templates/module.ltp
vendored
Normal 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 »</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 %}
|
||||
69
gamemodes/helix/docs/templates/sidebar.ltp
vendored
Normal file
69
gamemodes/helix/docs/templates/sidebar.ltp
vendored
Normal 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>
|
||||
345
gamemodes/helix/entities/entities/ix_item.lua
Normal file
345
gamemodes/helix/entities/entities/ix_item.lua
Normal 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
|
||||
74
gamemodes/helix/entities/entities/ix_money.lua
Normal file
74
gamemodes/helix/entities/entities/ix_money.lua
Normal 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
|
||||
147
gamemodes/helix/entities/entities/ix_shipment.lua
Normal file
147
gamemodes/helix/entities/entities/ix_shipment.lua
Normal 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
|
||||
649
gamemodes/helix/entities/weapons/ix_hands.lua
Normal file
649
gamemodes/helix/entities/weapons/ix_hands.lua
Normal 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
|
||||
60
gamemodes/helix/gamemode/cl_init.lua
Normal file
60
gamemodes/helix/gamemode/cl_init.lua
Normal 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")
|
||||
244
gamemodes/helix/gamemode/config/sh_config.lua
Normal file
244
gamemodes/helix/gamemode/config/sh_config.lua
Normal 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"
|
||||
})
|
||||
75
gamemodes/helix/gamemode/config/sh_options.lua
Normal file
75
gamemodes/helix/gamemode/config/sh_options.lua
Normal 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
|
||||
})
|
||||
608
gamemodes/helix/gamemode/core/cl_skin.lua
Normal file
608
gamemodes/helix/gamemode/core/cl_skin.lua
Normal 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()
|
||||
174
gamemodes/helix/gamemode/core/derma/cl_attribute.lua
Normal file
174
gamemodes/helix/gamemode/core/derma/cl_attribute.lua
Normal 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")
|
||||
207
gamemodes/helix/gamemode/core/derma/cl_bar.lua
Normal file
207
gamemodes/helix/gamemode/core/derma/cl_bar.lua
Normal 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
|
||||
513
gamemodes/helix/gamemode/core/derma/cl_business.lua
Normal file
513
gamemodes/helix/gamemode/core/derma/cl_business.lua
Normal 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)
|
||||
551
gamemodes/helix/gamemode/core/derma/cl_character.lua
Normal file
551
gamemodes/helix/gamemode/core/derma/cl_character.lua
Normal 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
|
||||
509
gamemodes/helix/gamemode/core/derma/cl_charcreate.lua
Normal file
509
gamemodes/helix/gamemode/core/derma/cl_charcreate.lua
Normal 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")
|
||||
486
gamemodes/helix/gamemode/core/derma/cl_charload.lua
Normal file
486
gamemodes/helix/gamemode/core/derma/cl_charload.lua
Normal 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")
|
||||
169
gamemodes/helix/gamemode/core/derma/cl_classes.lua
Normal file
169
gamemodes/helix/gamemode/core/derma/cl_classes.lua
Normal 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)
|
||||
210
gamemodes/helix/gamemode/core/derma/cl_config.lua
Normal file
210
gamemodes/helix/gamemode/core/derma/cl_config.lua
Normal 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")
|
||||
318
gamemodes/helix/gamemode/core/derma/cl_credits.lua
Normal file
318
gamemodes/helix/gamemode/core/derma/cl_credits.lua
Normal 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)
|
||||
271
gamemodes/helix/gamemode/core/derma/cl_dev_icon.lua
Normal file
271
gamemodes/helix/gamemode/core/derma/cl_dev_icon.lua
Normal 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)
|
||||
287
gamemodes/helix/gamemode/core/derma/cl_entitymenu.lua
Normal file
287
gamemodes/helix/gamemode/core/derma/cl_entitymenu.lua
Normal 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")
|
||||
933
gamemodes/helix/gamemode/core/derma/cl_generic.lua
Normal file
933
gamemodes/helix/gamemode/core/derma/cl_generic.lua
Normal 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")
|
||||
379
gamemodes/helix/gamemode/core/derma/cl_help.lua
Normal file
379
gamemodes/helix/gamemode/core/derma/cl_help.lua
Normal 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)
|
||||
283
gamemodes/helix/gamemode/core/derma/cl_information.lua
Normal file
283
gamemodes/helix/gamemode/core/derma/cl_information.lua
Normal 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)
|
||||
378
gamemodes/helix/gamemode/core/derma/cl_intro.lua
Normal file
378
gamemodes/helix/gamemode/core/derma/cl_intro.lua
Normal 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")
|
||||
806
gamemodes/helix/gamemode/core/derma/cl_inventory.lua
Normal file
806
gamemodes/helix/gamemode/core/derma/cl_inventory.lua
Normal 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)
|
||||
501
gamemodes/helix/gamemode/core/derma/cl_menu.lua
Normal file
501
gamemodes/helix/gamemode/core/derma/cl_menu.lua
Normal 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
|
||||
305
gamemodes/helix/gamemode/core/derma/cl_menubutton.lua
Normal file
305
gamemodes/helix/gamemode/core/derma/cl_menubutton.lua
Normal 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")
|
||||
130
gamemodes/helix/gamemode/core/derma/cl_modelpanel.lua
Normal file
130
gamemodes/helix/gamemode/core/derma/cl_modelpanel.lua
Normal 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")
|
||||
280
gamemodes/helix/gamemode/core/derma/cl_notice.lua
Normal file
280
gamemodes/helix/gamemode/core/derma/cl_notice.lua
Normal 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
|
||||
98
gamemodes/helix/gamemode/core/derma/cl_noticebar.lua
Normal file
98
gamemodes/helix/gamemode/core/derma/cl_noticebar.lua
Normal 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")
|
||||
253
gamemodes/helix/gamemode/core/derma/cl_overrides.lua
Normal file
253
gamemodes/helix/gamemode/core/derma/cl_overrides.lua
Normal 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)
|
||||
359
gamemodes/helix/gamemode/core/derma/cl_scoreboard.lua
Normal file
359
gamemodes/helix/gamemode/core/derma/cl_scoreboard.lua
Normal 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)
|
||||
781
gamemodes/helix/gamemode/core/derma/cl_settings.lua
Normal file
781
gamemodes/helix/gamemode/core/derma/cl_settings.lua
Normal 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)
|
||||
130
gamemodes/helix/gamemode/core/derma/cl_shipment.lua
Normal file
130
gamemodes/helix/gamemode/core/derma/cl_shipment.lua
Normal 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")
|
||||
107
gamemodes/helix/gamemode/core/derma/cl_spawnicon.lua
Normal file
107
gamemodes/helix/gamemode/core/derma/cl_spawnicon.lua
Normal 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")
|
||||
207
gamemodes/helix/gamemode/core/derma/cl_storage.lua
Normal file
207
gamemodes/helix/gamemode/core/derma/cl_storage.lua
Normal 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")
|
||||
312
gamemodes/helix/gamemode/core/derma/cl_subpanel.lua
Normal file
312
gamemodes/helix/gamemode/core/derma/cl_subpanel.lua
Normal 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")
|
||||
604
gamemodes/helix/gamemode/core/derma/cl_tooltip.lua
Normal file
604
gamemodes/helix/gamemode/core/derma/cl_tooltip.lua
Normal 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")
|
||||
1052
gamemodes/helix/gamemode/core/hooks/cl_hooks.lua
Normal file
1052
gamemodes/helix/gamemode/core/hooks/cl_hooks.lua
Normal file
File diff suppressed because it is too large
Load Diff
672
gamemodes/helix/gamemode/core/hooks/sh_hooks.lua
Normal file
672
gamemodes/helix/gamemode/core/hooks/sh_hooks.lua
Normal 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
|
||||
951
gamemodes/helix/gamemode/core/hooks/sv_hooks.lua
Normal file
951
gamemodes/helix/gamemode/core/hooks/sv_hooks.lua
Normal 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
|
||||
131
gamemodes/helix/gamemode/core/libs/cl_bar.lua
Normal file
131
gamemodes/helix/gamemode/core/libs/cl_bar.lua
Normal 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)
|
||||
146
gamemodes/helix/gamemode/core/libs/cl_hud.lua
Normal file
146
gamemodes/helix/gamemode/core/libs/cl_hud.lua
Normal 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
|
||||
506
gamemodes/helix/gamemode/core/libs/cl_markup.lua
Normal file
506
gamemodes/helix/gamemode/core/libs/cl_markup.lua
Normal 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, ">", ">")
|
||||
block.text = string.gsub(block.text, "<", "<")
|
||||
block.text = string.gsub(block.text, "&", "&")
|
||||
|
||||
for j=1,string.utf8len(block.text) do
|
||||
local ch = string.utf8sub(block.text,j,j)
|
||||
|
||||
if (ch == "\n") then
|
||||
if (thisY == 0) then
|
||||
thisY = lineHeight + texOffset;
|
||||
thisMaxY = lineHeight + texOffset;
|
||||
else
|
||||
lineHeight = thisY + texOffset
|
||||
end
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
end
|
||||
|
||||
xOffset = 0
|
||||
xSize = 0
|
||||
yOffset = yOffset + thisMaxY;
|
||||
thisY = 0
|
||||
curString = ""
|
||||
thisMaxY = 0
|
||||
elseif (ch == "\t") then
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
end
|
||||
|
||||
local xOldSize = xSize
|
||||
xSize = 0
|
||||
curString = ""
|
||||
local xOldOffset = xOffset
|
||||
xOffset = math.ceil( (xOffset + xOldSize) / 50 ) * 50
|
||||
|
||||
if (xOffset == xOldOffset) then
|
||||
xOffset = xOffset + 50
|
||||
end
|
||||
else
|
||||
local x,y = surface.GetTextSize(ch)
|
||||
|
||||
if (x == nil) then return end
|
||||
|
||||
if (maxwidth and maxwidth > x) then
|
||||
if (xOffset + xSize + x >= maxwidth) then
|
||||
|
||||
-- need to: find the previous space in the curString
|
||||
-- if we can't find one, take off the last character
|
||||
-- and add a -. add the character to ch
|
||||
-- and insert as a new block, incrementing the y etc
|
||||
|
||||
local lastSpacePos = string.utf8len(curString)
|
||||
for k=1,string.utf8len(curString) do
|
||||
local chspace = string.utf8sub(curString,k,k)
|
||||
if (chspace == " ") then
|
||||
lastSpacePos = k
|
||||
end
|
||||
end
|
||||
|
||||
if (lastSpacePos == string.utf8len(curString)) then
|
||||
ch = string.utf8sub(curString,lastSpacePos,lastSpacePos) .. ch
|
||||
j = lastSpacePos
|
||||
curString = string.utf8sub(curString, 1, lastSpacePos-1)
|
||||
else
|
||||
ch = string.utf8sub(curString,lastSpacePos+1) .. ch
|
||||
j = lastSpacePos+1
|
||||
curString = string.utf8sub(curString, 1, lastSpacePos)
|
||||
end
|
||||
|
||||
local m = 1
|
||||
while string.utf8sub(ch, m, m) == " " and m <= string.utf8len(ch) do
|
||||
m = m + 1
|
||||
end
|
||||
ch = string.utf8sub(ch, m)
|
||||
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
if (y1 > thisMaxY) then thisMaxY = y1; ymaxes[yOffset] = thisMaxY; lineHeight = y1; end
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
|
||||
xOffset = 0
|
||||
xSize = 0
|
||||
x,y = surface.GetTextSize(ch)
|
||||
yOffset = yOffset + thisMaxY;
|
||||
thisY = 0
|
||||
curString = ""
|
||||
thisMaxY = 0
|
||||
end
|
||||
end
|
||||
|
||||
curString = curString .. ch
|
||||
|
||||
thisY = y
|
||||
xSize = xSize + x
|
||||
|
||||
if (y > thisMaxY) then thisMaxY = y; ymaxes[yOffset] = thisMaxY; lineHeight = y; end
|
||||
end
|
||||
end
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
|
||||
lineHeight = thisY
|
||||
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
xOffset = xOffset + x1
|
||||
end
|
||||
xSize = 0
|
||||
elseif (block.texture) then
|
||||
local newBlock = table.Copy(block)
|
||||
newBlock.colour = block.colour or {r = 255, g = 255, b = 255, a = 255}
|
||||
newBlock.thisX = block.w
|
||||
newBlock.thisY = block.h
|
||||
newBlock.offset = {
|
||||
x = xOffset,
|
||||
y = 0
|
||||
}
|
||||
|
||||
table.insert(new_block_list, newBlock)
|
||||
xOffset = xOffset + block.w + 1
|
||||
texOffset = block.h / 2
|
||||
end
|
||||
end
|
||||
|
||||
local totalHeight = 0
|
||||
for i = 1, #new_block_list do
|
||||
local block = new_block_list[i]
|
||||
block.height = ymaxes[block.offset.y]
|
||||
|
||||
if (block.texture) then
|
||||
block.offset.y = ymaxes[0] * 0.5 - block.h * 0.5
|
||||
end
|
||||
|
||||
if (block.height and block.offset.y + block.height > totalHeight) then
|
||||
totalHeight = block.offset.y + block.height
|
||||
end
|
||||
end
|
||||
|
||||
local newObject = MarkupObject:create()
|
||||
newObject.totalHeight = totalHeight
|
||||
newObject.totalWidth = xMax
|
||||
newObject.blocks = new_block_list
|
||||
return newObject
|
||||
end
|
||||
68
gamemodes/helix/gamemode/core/libs/cl_networking.lua
Normal file
68
gamemodes/helix/gamemode/core/libs/cl_networking.lua
Normal 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
|
||||
160
gamemodes/helix/gamemode/core/libs/sh_animation.lua
Normal file
160
gamemodes/helix/gamemode/core/libs/sh_animation.lua
Normal 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
|
||||
571
gamemodes/helix/gamemode/core/libs/sh_anims.lua
Normal file
571
gamemodes/helix/gamemode/core/libs/sh_anims.lua
Normal 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
|
||||
201
gamemodes/helix/gamemode/core/libs/sh_attribs.lua
Normal file
201
gamemodes/helix/gamemode/core/libs/sh_attribs.lua
Normal 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
|
||||
161
gamemodes/helix/gamemode/core/libs/sh_business.lua
Normal file
161
gamemodes/helix/gamemode/core/libs/sh_business.lua
Normal 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
|
||||
1282
gamemodes/helix/gamemode/core/libs/sh_character.lua
Normal file
1282
gamemodes/helix/gamemode/core/libs/sh_character.lua
Normal file
File diff suppressed because it is too large
Load Diff
758
gamemodes/helix/gamemode/core/libs/sh_chatbox.lua
Normal file
758
gamemodes/helix/gamemode/core/libs/sh_chatbox.lua
Normal 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")
|
||||
212
gamemodes/helix/gamemode/core/libs/sh_class.lua
Normal file
212
gamemodes/helix/gamemode/core/libs/sh_class.lua
Normal 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
|
||||
636
gamemodes/helix/gamemode/core/libs/sh_command.lua
Normal file
636
gamemodes/helix/gamemode/core/libs/sh_command.lua
Normal 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
|
||||
166
gamemodes/helix/gamemode/core/libs/sh_compnettable.lua
Normal file
166
gamemodes/helix/gamemode/core/libs/sh_compnettable.lua
Normal 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
|
||||
121
gamemodes/helix/gamemode/core/libs/sh_currency.lua
Normal file
121
gamemodes/helix/gamemode/core/libs/sh_currency.lua
Normal 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
|
||||
156
gamemodes/helix/gamemode/core/libs/sh_date.lua
Normal file
156
gamemodes/helix/gamemode/core/libs/sh_date.lua
Normal 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
|
||||
135
gamemodes/helix/gamemode/core/libs/sh_faction.lua
Normal file
135
gamemodes/helix/gamemode/core/libs/sh_faction.lua
Normal 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
|
||||
213
gamemodes/helix/gamemode/core/libs/sh_flag.lua
Normal file
213
gamemodes/helix/gamemode/core/libs/sh_flag.lua
Normal 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
|
||||
186
gamemodes/helix/gamemode/core/libs/sh_inventory.lua
Normal file
186
gamemodes/helix/gamemode/core/libs/sh_inventory.lua
Normal 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
|
||||
879
gamemodes/helix/gamemode/core/libs/sh_item.lua
Normal file
879
gamemodes/helix/gamemode/core/libs/sh_item.lua
Normal 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"
|
||||
})
|
||||
138
gamemodes/helix/gamemode/core/libs/sh_language.lua
Normal file
138
gamemodes/helix/gamemode/core/libs/sh_language.lua
Normal 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
|
||||
169
gamemodes/helix/gamemode/core/libs/sh_log.lua
Normal file
169
gamemodes/helix/gamemode/core/libs/sh_log.lua
Normal 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
|
||||
119
gamemodes/helix/gamemode/core/libs/sh_menu.lua
Normal file
119
gamemodes/helix/gamemode/core/libs/sh_menu.lua
Normal 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
|
||||
|
||||
154
gamemodes/helix/gamemode/core/libs/sh_notice.lua
Normal file
154
gamemodes/helix/gamemode/core/libs/sh_notice.lua
Normal 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
|
||||
381
gamemodes/helix/gamemode/core/libs/sh_option.lua
Normal file
381
gamemodes/helix/gamemode/core/libs/sh_option.lua
Normal 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
|
||||
153
gamemodes/helix/gamemode/core/libs/sh_player.lua
Normal file
153
gamemodes/helix/gamemode/core/libs/sh_player.lua
Normal 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
|
||||
526
gamemodes/helix/gamemode/core/libs/sh_plugin.lua
Normal file
526
gamemodes/helix/gamemode/core/libs/sh_plugin.lua
Normal 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
|
||||
491
gamemodes/helix/gamemode/core/libs/sh_storage.lua
Normal file
491
gamemodes/helix/gamemode/core/libs/sh_storage.lua
Normal 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
|
||||
204
gamemodes/helix/gamemode/core/libs/sv_database.lua
Normal file
204
gamemodes/helix/gamemode/core/libs/sv_database.lua
Normal 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)
|
||||
245
gamemodes/helix/gamemode/core/libs/sv_networking.lua
Normal file
245
gamemodes/helix/gamemode/core/libs/sv_networking.lua
Normal 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
|
||||
125
gamemodes/helix/gamemode/core/libs/sv_player.lua
Normal file
125
gamemodes/helix/gamemode/core/libs/sv_player.lua
Normal 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
|
||||
362
gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua
vendored
Normal file
362
gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua
vendored
Normal 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
|
||||
1870
gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua
vendored
Normal file
1870
gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua
vendored
Normal file
File diff suppressed because it is too large
Load Diff
560
gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua
vendored
Normal file
560
gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua
vendored
Normal 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
|
||||
791
gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua
vendored
Normal file
791
gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua
vendored
Normal 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
|
||||
193
gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua
vendored
Normal file
193
gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua
vendored
Normal 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
|
||||
174
gamemodes/helix/gamemode/core/libs/thirdparty/sh_netstream2.lua
vendored
Normal file
174
gamemodes/helix/gamemode/core/libs/thirdparty/sh_netstream2.lua
vendored
Normal 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;
|
||||
411
gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua
vendored
Normal file
411
gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua
vendored
Normal 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
|
||||
385
gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua
vendored
Normal file
385
gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua
vendored
Normal 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
|
||||
338
gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua
vendored
Normal file
338
gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua
vendored
Normal 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
|
||||
615
gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua
vendored
Normal file
615
gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua
vendored
Normal 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
|
||||
663
gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua
vendored
Normal file
663
gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua
vendored
Normal 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
|
||||
397
gamemodes/helix/gamemode/core/meta/sh_character.lua
Normal file
397
gamemodes/helix/gamemode/core/meta/sh_character.lua
Normal 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
|
||||
227
gamemodes/helix/gamemode/core/meta/sh_entity.lua
Normal file
227
gamemodes/helix/gamemode/core/meta/sh_entity.lua
Normal 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
Reference in New Issue
Block a user